{"id":14969595,"url":"https://github.com/exlll/configlib","last_synced_at":"2025-12-24T04:36:17.384Z","repository":{"id":43666579,"uuid":"78556475","full_name":"Exlll/ConfigLib","owner":"Exlll","description":"A Minecraft library for saving, loading, updating, and commenting YAML configuration files","archived":false,"fork":false,"pushed_at":"2025-05-01T23:22:14.000Z","size":630,"stargazers_count":167,"open_issues_count":2,"forks_count":19,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-05-02T00:24:16.429Z","etag":null,"topics":["bukkit","bukkit-plugins","bungeecord","bungeecord-plugin","libary","minecraft","minecraft-plugin","minecraft-plugin-development","paper","paper-plugin","papermc","plugins","spigot","spigot-plugins","waterfall","waterfall-plugin","yaml"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Exlll.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2017-01-10T17:22:29.000Z","updated_at":"2025-04-30T20:52:26.000Z","dependencies_parsed_at":"2022-07-18T06:30:29.255Z","dependency_job_id":"7cb88331-21da-4bbf-a92f-26868562255f","html_url":"https://github.com/Exlll/ConfigLib","commit_stats":{"total_commits":89,"total_committers":6,"mean_commits":"14.833333333333334","dds":0.0898876404494382,"last_synced_commit":"093ded264d3d35b10746a57b23b2b3892d15b785"},"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Exlll%2FConfigLib","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Exlll%2FConfigLib/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Exlll%2FConfigLib/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Exlll%2FConfigLib/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Exlll","download_url":"https://codeload.github.com/Exlll/ConfigLib/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253757069,"owners_count":21959328,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bukkit","bukkit-plugins","bungeecord","bungeecord-plugin","libary","minecraft","minecraft-plugin","minecraft-plugin-development","paper","paper-plugin","papermc","plugins","spigot","spigot-plugins","waterfall","waterfall-plugin","yaml"],"created_at":"2024-09-24T13:42:05.679Z","updated_at":"2025-12-24T04:36:17.373Z","avatar_url":"https://github.com/Exlll.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ConfigLib\n\n**A Minecraft library for saving, loading, updating, and commenting YAML\nconfiguration files.**\n\nThis library facilitates creating, saving, loading, updating, and commenting\nYAML configuration files. It does so by automatically mapping instances of\nconfiguration classes to serializable maps which are first transformed into YAML\nand then saved to some specified file.\n\nInformation on how to [import](#import) this library can be found at the end of\nthis documentation. For a step-by-step tutorial that shows most features of this\nlibrary in action check out\nthe [Tutorial](https://github.com/Exlll/ConfigLib/wiki/Tutorial) page on the\nwiki!\n\n## Features\n\n* Automatic creation, saving, loading, and updating of configuration files\n* Support for comments through annotations\n* Support for all primitive types, their wrapper types, and strings\n* Support for all Java enums, records, and POJOs (+ inheritance!)\n* Support for (nested) lists, sets, arrays, and maps\n* Support for `BigInteger` and `BigDecimal`\n* Support for `LocalDate`, `LocalTime`, `LocalDateTime`, and `Instant`\n* Support for `UUID`, `File`, `Path`, `URL`, and `URI`\n* Support for Bukkit's `ConfigurationSerializable` types (e.g. `ItemStack`)\n* Option to format the names of configuration elements\n* Option to exclude fields from being converted\n* Option to customize null handling\n* Option to customize serialization by providing your own serializers\n* Option to add headers and footers to configuration files\n* Option to overwrite configuration values with environment variables\n* ...and a few more!\n\n## Usage example\n\nThis section contains a short usage example to get you started. The whole range\nof features is discussed in the following sections. Information on how\nto [import](#import) this library is located at the end of this documentation.\n\nFor a step-by-step tutorial with a more advanced example check out\nthe [Tutorial](https://github.com/Exlll/ConfigLib/wiki/Tutorial) page on the\nwiki.\n\nIf you want support for Bukkit classes like `ItemStack`, check out\nthe [Configuration properties](#configuration-properties) section.\n\n```java\npublic final class Example {\n    // * To create a configuration annotate a class with @Configuration and make sure that\n    //   it has a no-args constructor.\n    // * Now add fields to that class and assign them default values.\n    // * That's it! Fields can be private; setters are not required.\n    @Configuration\n    public static class BaseConfiguration {\n        private String host = \"127.0.0.1\";\n        private int port = 1234;\n        // The library supports lists, sets, and maps.\n        private Set\u003cString\u003e blockedAddresses = Set.of(\"8.8.8.8\");\n        // Fields can be ignored by making them final, transient, static or by\n        // annotating them with @Ignore.\n        private final double ignoreMe = 3.14;\n    }\n\n    // This library supports records; no @Configuration annotation required\n    public record User(\n            String username,\n            @Comment(\"Please choose a strong password.\")\n            String password\n    ) {}\n\n    // Subclassing of configurations and nesting of configurations in other configurations\n    // is also supported. Subclasses don't need to be annotated again.\n    public static final class UserConfiguration extends BaseConfiguration {\n        // You can add comments with the @Comment annotation. Each string in the comment\n        // array is written (as a comment) on a new line.\n        @Comment({\"The admin user has full access.\", \"Choose a proper password!\"})\n        User admin = new User(\"root\", \"toor\"); // The User class is a record!\n        List\u003cUser\u003e blockedUsers = List.of(\n                new User(\"user1\", null), // null values are supported\n                new User(\"user2\", null)\n        );\n    }\n\n    public static void main(String[] args) {\n        var configFile = Paths.get(\"/tmp/config.yml\");\n        var config = new UserConfiguration();\n\n        // Save an instance to the configuration file\n        YamlConfigurations.save(configFile, UserConfiguration.class, config);\n\n        // Load a new instance from the configuration file\n        config = YamlConfigurations.load(configFile, UserConfiguration.class);\n        System.out.println(config.admin.username);\n        System.out.println(config.blockedUsers);\n\n        // Modify the configuration and save it again\n        config.blockedUsers.add(new User(\"user3\", \"pass3\"));\n        YamlConfigurations.save(configFile, UserConfiguration.class, config);\n    }\n}\n```\n\nBy running the above code, a new YAML configuration is created\nat `/tmp/config.yml`. Its content looks like this:\n\n```yaml\nhost: 127.0.0.1\nport: 1234\nblockedAddresses:\n  - 8.8.8.8\n# The admin user has full access.\n# Choose a proper password!\nadmin:\n  username: root\n  # Please choose a strong password.\n  password: toor\nblockedUsers:\n  - username: user1\n  - username: user2\n  - username: user3\n    password: pass3\n```\n\nTwo things are noticeable here:\n\n1. Not every user in the `blockedUsers` list has a `password` mapping. This is\n   because null values are not output by default. That behavior can be changed\n   by the builder.\n2. The password of the user with username `user3` has no comment. This is due to\n   limitations of the YAML library. Configurations in lists, sets, or maps\n   cannot have their comments printed.\n\n## General information\n\nIn the following sections the term _configuration type_ refers to any Java\nrecord type or to any non-generic class that is directly or indirectly (i.e.\nthrough subclassing) annotated with`@de.exlll.configlib.Configuration`.\nAccordingly, the term _configuration_ refers to an instance of such a type. A\n_configuration element_ is either a class field or a record component of a\nconfiguration type.\n\n### Declaring configuration types\n\nTo declare a configuration type, either define a Java record or annotate a class\nwith `@Configuration` and make sure that it has a no-args constructor. The\nno-args constructor can be `private`. Inner classes (i.e. the ones that are\nnested but not `static`) have an implicit synthetic constructor with at least\none argument and are, therefore, not supported.\n\nNow simply add components to your record or fields to your class whose type is\nany of the supported types listed in the next section. You can (and should)\ninitialize all fields of a configuration class with non-null default values.\n\n### Supported types\n\nA configuration type may only contain configuration elements of the following\ntypes:\n\n| Type class                  | Types                                                              |\n|-----------------------------|--------------------------------------------------------------------|\n| Boolean types               | `boolean`, and `Boolean`                                           |\n| Integer types               | `byte`, `short`, `int`, `long`, and their respective wrapper types |\n| Floating point types        | `float`, `double`, and their respective wrapper types              |\n| Characters and strings      | `char`, `Character`, `String`                                      |\n| Big numeric types           | `BigInteger`, `BigDecimal`                                         |\n| Time related types          | `LocalTime`, `LocalDate`, `LocalDateTime`, `Instant`               |\n| Utility types               | `UUID`, `File`, `Path`, `URL`, `URI`                               |\n| Enums                       | Any Java enum                                                      |\n| Configurations              | Any Java record or any class annotated with `@Configuration`       |\n| `ConfigurationSerializable` | All Bukkit classes that implement this interface, like `ItemStack` |\n| Collections                 | (Nested) Lists, sets, maps*, or arrays of previously listed types  |\n\n(*) Map keys can only be of simple or enum type, i.e. they cannot be in\nthe `Collections`, `Configurations`, or `ConfigurationSerializable` type class.\n\nFor all types that are not listed in the table above, you can provide your\nown [custom serializer](#custom-serializers).\n\n#### Examples of supported types\n\nThe following class contains examples of types that this library supports:\n\n```java\npublic final class SupportedTypes {\n    boolean supported;\n    Character supported;\n    String supported;\n    LocalTime supported;\n    UUID supported;\n    ExampleEnum supported;    // where 'ExampleEnum' is some Java enum type\n    ExampleConfig supported;  // where 'ExampleConfig' is a class annotated with @Configuration\n    ExampleRecord supported;  // where 'ExampleRecord' is a Java record\n\n    /* collection types */\n    List\u003cBigInteger\u003e supported;\n    Set\u003cDouble\u003e supported;\n    LocalDate[] supported;\n    Map\u003cExampleEnum, ExampleConfig\u003e supported;\n\n    /* nested collection types */\n    List\u003cMap\u003cExampleEnum, LocalDate\u003e\u003e supported;\n    int[][] supported;\n    Map\u003cInteger, List\u003cMap\u003cShort, Set\u003cExampleRecord\u003e\u003e\u003e\u003e supported;\n\n    // supported if a custom serializer is registered\n    java.awt.Point supported;\n\n    // supported when a special properties object is used (explained further below)\n    org.bukkit.inventory.ItemStack supported;\n}\n```\n\n\u003cdetails\u003e\n \u003csummary\u003eExamples of unsupported types\u003c/summary\u003e\n\nThe following class contains examples of types that this library does (and will)\nnot support:\n\n```java\npublic final class UnsupportedTypes\u003cT\u003e {\n    Map\u003cPoint, String\u003e unsupported;        // invalid map key\n    Map\u003cList\u003cString\u003e, String\u003e unsupported; // invalid map key\n    Box\u003cString\u003e unsupported;               // custom parameterized type\n    List\u003c? extends String\u003e unsupported;    // wildcard type\n    List\u003c?\u003e unsupported;                   // wildcard type\n    List\u003c?\u003e[] unsupported;                 // wildcard type\n    T unsupported;                         // type variable\n    List unsupported;                      // raw type\n    List[] unsupported;                    // raw type\n    List\u003cString\u003e[] unsupported;            // generic array type\n    Set\u003cInteger\u003e[] unsupported;            // generic array type\n    Map\u003cByte, Byte\u003e[] unsupported;         // generic array type\n}\n```\n\n**NOTE:** Even though this library does not support these types, it is still\npossible to serialize them by providing a custom serializer via\nthe [`@SerializeWith`](#the-serializewith-annotation) annotation. That\nserializer then has to be applied to top-level type (i.e. `nesting` must be set\nto `0`, which is the default).\n\n\u003c/details\u003e\n\n### Loading and saving configurations\n\nThere are two ways to load and save configurations. Which way you choose depends\non your liking. Both ways have five methods in common:\n\n* The `save` method converts a configuration to a string in YAML format and\n  saves that string to a file. The file is created if it does not exist and is\n  overwritten otherwise.\n* The `load` method creates a new configuration instance and populates it with\n  values taken from a file. For classes, the no-args constructor is used to\n  create a new instance. For records, the canonical constructor is called.\n* The `update` method is a combination of `load` and `save` and the method you'd\n  usually want to use: it takes care of creating the configuration file if it\n  does not exist and otherwise updates it to reflect changes to (the\n  configuration elements of) the configuration type.\n* The `write` method works the same way as the `save` method but writes the\n  string to a `java.io.OutputStream`.\n* The `read` method works the same way as the `load` method but reads the values\n  from a `java.io.InputStream`.\n\n\u003cdetails\u003e\n \u003csummary\u003eExample of \u003ccode\u003eupdate\u003c/code\u003e behavior when configuration file exists\u003c/summary\u003e\n\nLet's say you have the following configuration type\n\n```java \n@Configuration \npublic final class C {\n    int i = 10; \n    int j = 11; \n}\n```\n\n... and a YAML configuration file that contains:\n\n```yaml\ni: 20\nk: 30\n```\n\nNow, when you call the `update` method for that configuration type and file\nusing any of the two options listed below, the configuration instance\nthat `update` returns will have its `i` variable initialized to `20` and its `j`\nvariable will have its default of `11`. After the operation, the configuration\nfile will contain the following content (note that `k` has been dropped):\n\n```yaml\ni: 20\nj: 11\n```\n\n\u003chr\u003e\n\n\u003c/details\u003e\n\nTo exemplify the usage of these five methods we assume for the following\nsections that you have implemented the configuration type below and have access\nto some regular `java.nio.file.Path` object `configurationFile`.\n\n```java \n@Configuration\npublic final class Config { /* some fields */ }\n```\n\n#### Option 1\n\nThe first option is to create a `YamlConfigurationStore` instance and use it to\nsave, load, or update configurations.\n\n```java \nYamlConfigurationProperties properties = YamlConfigurationProperties.newBuilder().build();\nYamlConfigurationStore\u003cConfig\u003e store = new YamlConfigurationStore\u003c\u003e(Config.class, properties);\n\nConfig config1 = store.load(configurationFile);\nstore.save(config1, configurationFile);\nConfig config2 = store.update(configurationFile);\n```\n\nUsing a `YamlConfigurationStore` directly is always more efficient than the\nsecond option show below, especially if you are calling any of its method\nmultiple times.\n\n#### Option 2\n\nThe second option is to use the static methods from the `YamlConfigurations`\nclass.\n\n```java \nConfig config1 = YamlConfigurations.load(configurationFile, Config.class);\nYamlConfigurations.save(configurationFile, Config.class, config1);\nConfig config2 = YamlConfigurations.update(configurationFile, Config.class);\n```\n\nEach of these methods has two additional overloads: One that takes a properties\nobject and another that lets you configure a properties object builder. For\nexample, the overloads of the `load` method are:\n\n```java \n// overload 1\nYamlConfigurationProperties properties = YamlConfigurationProperties.newBuilder().build();\nConfig config1 = YamlConfigurations.load(configurationFile, Config.class, properties);\n\n// overload 2\nConfig config2 = YamlConfigurations.load(\n    configurationFile,\n    Config.class,\n    builder -\u003e builder.inputNulls(true).outputNulls(false)\n); \n```\n\n\u003chr\u003e\n\nAll five methods can also be passed a Java record instead of a class. To provide\ndefault values for records when calling the `update` method, you can add a\nconstructor with no parameters that initializes its components. This constructor\nis only called if the configuration file does not exist.\n\n```java \nrecord User(String name, String email) {\n    User() { this(\"John Doe\", \"john@doe.com\"); }\n}\nUser user = YamlConfigurations.update(configurationFile, User.class);\n```\n\n### Configuration properties\n\nInstances of the `ConfigurationProperties` class allow customization of how\nconfigurations are stored and loaded. To create such an instance, instantiate a\nnew builder using the `YamlConfigurationProperties.newBuilder()` method,\nconfigure it, and finally call its `build()` method. Alternatively, you can use\nthe `toBuilder()` method of an existing `YamlConfigurationProperties` to create\na new builder that is initialized with values takes from the properties object.\n\nCheck out the methods of the builder class to see which configuration options\nare available.\n\n#### Support for Bukkit classes like `ItemStack`\n\nThere is a special `YamlConfigurationProperties` object with\nname `BUKKIT_DEFAULT_PROPERTIES` that adds support for\nBukkit's `ConfigurationSerializable` types. If you want to use any of these\ntypes in your configuration, you have to use that object as a starting point:\n\n```java \nYamlConfigurationProperties properties = ConfigLib.BUKKIT_DEFAULT_PROPERTIES.toBuilder()\n     // ...further configure the builder...\n    .build();\n```\n\nTo get access to this object, you have to import `configlib-paper` instead\nof `configlib-yaml` as described in the [Import](#import) section.\n\n### Comments\n\nThe configuration elements of a configuration type can be annotated with\nthe `@Comment` annotation. This annotation takes an array of strings. Each of\nthese strings is written onto a new line as a comment. The strings can\ncontain `\\n` characters. Empty strings are written as newlines (not as\ncomments).\n\nIf a configuration type _C_ that defines comments is used (as a configuration\nelement) within another configuration type, the comments of _C_ are written with\nthe proper indentation. However, if instances of _C_ are stored inside a\ncollection, their comments are not printed when the collection is written.\n\nSerializing the following configuration as YAML ...\n\n```java \n@Configuration\npublic final class ExampleConfiguration {\n    @Comment({\"Hello\", \"\", \" \", \"World\"})\n    private String commentedField = \"commented field\";\n}\n```\n\n... results in the YAML file shown below:\n\n```yaml\n # Hello\n\n #  \n # World\n commentedField: commented field\n```\n\nSimilarly, if you define the following record configuration and save it ...\n\n```java \nrecord Address(@Comment(\"The street\") String street) {}\nrecord User(@Comment(\"The name\") String name, @Comment(\"The address\") Address address) {}\n\nUser user = new User(\"John Doe\", new Address(\"10 Downing St\"));\n```\n\n... you get:\n\n```yaml\n# The name\nname: John Doe\n# The address\naddress:\n  # The street\n  street: 10 Downing St\n```\n\n### Subclassing\n\nSubclassing of configurations types is supported. Subclasses of configuration\ntypes don't need to be annotated with `@Configuration`. When a configuration is\nwritten, the fields of parent classes are written before the fields of the child\nin a top to bottom manner. Parent configurations can be `abstract`.\n\n#### Shadowing of fields\n\nShadowing of fields refers to the situation where a subclass of configuration\nhas a field that has the same name as a field in one of its super classes.\nShadowing of fields is currently not supported. (This restriction might easily\nbe lifted. If you need this feature, please open an issue and describe how to\nhandle name clashes.)\n\n### Ignoring and filtering fields\n\nFields that are `final`, `static`, `transient` or annotated with `@Ignore` are\nneither serialized nor updated during deserialization. You can filter out\nadditional fields by providing an instance of `FieldFilter` to the configuration\nproperties. Record components cannot be filtered.\n\n### Handling of missing and `null` values\n\n#### Missing values\n\nWhen a configuration file is read, values that correspond to a configuration\nelement might be missing. That can happen, for example, when somebody deleted\nthat value from the configuration file, when you add configuration elements to\nyour configuration type, or when the `NameFormatter` that was used to create\nthat file is replaced.\n\nIn such cases, fields of configuration classes keep the default value you\nassigned to them and record components are initialized with the default value of\ntheir corresponding type.\n\n#### Null values\n\n**NOTE:** Null values written to a configuration file generally don't give any\nindication about which kinds of values the configuration expects. Therefore,\nthey not only make it harder for the users of that configuration file to\nproperly configure it, but they might also prevent loading a configuration if\nthe values the users set are of the wrong type.\n\nAlthough strongly discouraged, null values are supported\nand `ConfigurationProperties` let you configure how they are handled when\nserializing and deserializing a configuration:\n\n* By setting `outputNulls` to false, configuration elements, and collection\n  elements that are null are not output. Any comments that belong to such fields\n  are also not written.\n* By setting `inputNulls` to false, null values read from the configuration file\n  are treated as missing and are, therefore, handled as described in the section\n  above.\n* By setting `inputNulls` to true, null values read from the configuration file\n  overwrite the corresponding default values of a configuration class with null\n  or set the component value of a record type to null. If the configuration\n  element type is primitive, an exception is thrown.\n\nThe following code forbids null values to be output but allows null values to be\ninput. By default, both are forbidden which makes the call to `outputNulls` in\nthis case redundant.\n\n```java \nYamlConfigurationProperties.newBuilder()\n        .outputNulls(false)\n        .inputNulls(true)\n        .build();\n```\n\n### Formatting the names of configuration elements\n\nYou can define how the names of configuration elements are formatted by\nconfiguring the configuration properties with a custom formatter. Formatters are\nimplementations of the `NameFormatter` interface. You can implement this\ninterface yourself or use one of the several formatters this library provides.\nThese pre-defined formatters can be found in the `NameFormatters` class.\n\nThe following code formats fields using the `IDENTITY` formatter (which is the\ndefault).\n\n```java \nYamlConfigurationProperties.newBuilder()\n        .setNameFormatter(NameFormatters.IDENTITY)\n        .build();\n```\n\n### Type conversion and serializer selection\n\nBefore instances of the types listed in the [supported types](#supported-types)\nsection can be stored, they need to be converted into serializable types (i.e.\ninto types the underlying YAML library knows how to handle). The conversion\nhappens according to the following table:\n\n| Source type                 | Target type      |\n|-----------------------------|------------------|\n| Boolean types               | `Boolean`        |\n| Integer types               | `Long`           |\n| Floating point types        | `Double`         |\n| Characters and strings      | `String`         |\n| Big numeric types           | `String`         |\n| Time related types          | `String`         |\n| Utility types               | `String`         |\n| Enums                       | `String`         |\n| Configurations              | `Map\u003cString, ?\u003e` |\n| `Set\u003cS\u003e`                    | `List\u003cT\u003e`        |\n| `List\u003cS\u003e`                   | `List\u003cT\u003e`        |\n| `S[]`                       | `List\u003cT\u003e`        |\n| `Map\u003cS1, S2\u003e`               | `Map\u003cT1, T2\u003e`    |\n| `ConfigurationSerializable` | `String`         |\n\n#### Serializer selection\n\nTo convert the value of a configuration element `E` with (source) type `S` into\na serializable value of some target type, a serializer has to be selected.\nSerializers are instances of the `de.exlll.configlib.Serializer` interface and\nare selected based on `S`. Put differently, serializers are, by default, always\nselected based on the compile-time type of `E` and never on the runtime type of\nits value.\n\n\u003cdetails\u003e\n \u003csummary\u003eWhy should I care about this?\u003c/summary\u003e\n\nThis distinction makes a difference (and might lead to confusion) when you have\nconfiguration elements that are configuration classes, and you extend those\nclasses. Concretely, assume you have written two configuration classes `A`\nand `B` where `B extends A`. Then, if you use `A a = new B()` in your main\nconfiguration, only the fields of a `A` will be stored when you save your main\nconfiguration. That is because the serializer of field `a` was selected based on\nthe compile-time type of `a` which is `A` and not `B`. The same happens if you\nhave a `List\u003cA\u003e` and put instances of `B` (or some other subclass of `A`) in it.\n\nIf you need such behavior, have a look at\nthe [`@Polymorphic`](#the-polymorphic-annotation) annotation.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n \u003csummary\u003eOrder of serializer selection\u003c/summary\u003e\n\nYou can override the default selection by annotating a configuration\nelement with [`@SerializeWith`](#the-serializewith-annotation), by annotating a\ntype with `@SerializeWith`, or by adding your own serializer for `S` to the\nconfiguration properties. When you do so, it can happen that there multiple\nserializers available for a particular configuration element and its type. In\nthat case, one of them chosen according to the following precedence rules:\n\n1. If the element is annotated with `@SerializeWith` and the `nesting` matches,\n   the serializer referenced by the annotation is selected.\n2. Otherwise, if the `ConfigurationProperties` contain a serializer for the type\n   in question, that serializer is returned.\n    * Serializers created by factories that were added\n      through `addSerializerFactory` for some type take precedence over\n      serializers added by `addSerializer` for the same type.\n3. If the type is annotated `@SerializeWith`, the serializer referenced by the\n   annotation is selected.\n4. If the type is annotated with an annotation which is annotated\n   with `@SerializeWith`, the serializer referenced by `@SerializeWith` is\n   returned.\n5. If this library defines a serializer for that type, that serializer is\n   selected.\n6. Ultimately, if no serializer can be found, an exception is thrown.\n\nFor lists, sets, and maps, the algorithm is applied to their generic type\narguments recursively first.\n\n\u003c/details\u003e\n\n##### The `@SerializeWith` annotation\n\nThe `@SerializeWith` annotation enforces the use of the specified serializer for\na configuration element or type. It can be applied to configuration elements\n(i.e. class fields and record components), to types, and to other annotations.\n\n```java \n@SerializeWith(serializer = MyPointSerializer.class)\nPoint point;\n```\n\n```java \n@SerializeWith(serializer = SomeClassSerializer.class)\npublic final class SomeClass {/* ... */} \n```\n\nThe serializer referenced by this annotation is selected regardless of whether\nthe annotated type or type of configuration element matches the type the\nserializer expects.\n\nIf the annotation is applied to a configuration element and that element is an\narray, list, set, or map, a nesting level can be set to apply the serializer not\nto the top-level type but to its elements. For maps, the serializer is applied\nto the values and not the keys.\n\n```java \n@SerializeWith(serializer = MySetSerializer.class, nesting = 1)\nList\u003cSet\u003cString\u003e\u003e list;\n```\n\nSetting `nesting` to an invalid value, i.e. a negative one or one that is\ngreater than the number of levels the element actually has, results in the\nserializer not being selected. For type annotations, the `nesting` has no\neffect.\n\n\u003cdetails\u003e\n \u003csummary\u003eMore \u003ccode\u003enesting\u003c/code\u003e examples\u003c/summary\u003e\n\nIn this example...\n\n```java \n@SerializeWith(serializer = MySetSerializer.class, nesting = 1)\nList\u003cSet\u003cString\u003e\u003e list;\n```\n\n* a nesting of `0` would apply the serializer to `list` (which is of\n  type `List\u003cSet\u003cString\u003e\u003e`),\n* a nesting of `1` would apply it to the `Set\u003cString\u003e` elements within `list`,\n  and\n* a nesting of `2` would apply it to the strings within the sets of `list`.\n\nHowever, since the referenced serializer `MySetSerializer` most likely\nexpects `Set`s as input, setting `nesting` to `0` or `2` would result in an\nexception being thrown when the configuration is serialized.\n\nSome more examples:\n\n```java \n// MyListSerializer is applied to 'list'\n@SerializeWith(serializer = MyListSerializer.class)\nList\u003cSet\u003cString\u003e\u003e list;\n\n// MySetSerializer is applied to the Set\u003cString\u003e elements of 'list'\n@SerializeWith(serializer = MySetSerializer.class, nesting = 1)\nList\u003cSet\u003cString\u003e\u003e list;\n\n// MyStringSerializer is applied to the strings within the set elements of 'list'\n@SerializeWith(serializer = MyStringSerializer.class, nesting = 2)\nList\u003cSet\u003cString\u003e\u003e list;\n\n// MyMap0Serializer is applied to 'map'\n@SerializeWith(serializer = MyMap0Serializer.class)\nMap\u003cInteger, Map\u003cString, Double\u003e\u003e map;\n\n// MyMap1Serializer is applied to the Map\u003cString, Double\u003e values of 'map'\n@SerializeWith(serializer = MyMap1Serializer.class, nesting = 1)\nMap\u003cInteger, Map\u003cString, Double\u003e\u003e map;\n\n// MyDoubleSerializer is applied to the doubles within the nested values of 'map'\n@SerializeWith(serializer = MyDoubleSerializer.class, nesting = 2)\nMap\u003cInteger, Map\u003cString, Double\u003e\u003e map; \n```\n\n\u003c/details\u003e\n\n#### The `@Polymorphic` annotation\n\nThe `@Polymorphic` annotation indicates that the annotated type is polymorphic.\nSerializers for polymorphic types are not selected based on the compile-time\ntypes of configuration elements, but instead are chosen at runtime based on the\nactual types of their values.\n\nThis enables adding instances of subclasses / implementations of a polymorphic\ntype to collections. The subtypes must be valid configurations.\n\n```java \n@Polymorphic\n@Configuration\nstatic abstract class A { ... }\n\nstatic final class Impl1 extends A { ... }\nstatic final class Impl2 extends A { ... }\n    \nList\u003cA\u003e as = List.of(new Impl1(...), new Impl2(...), ...); \n```\n\nFor correct deserialization, if an instance of polymorphic type (or one of its\nimplementations / subclasses) is serialized, an additional property that holds\ntype information is added to its serialization. By default, that type\ninformation is the Java class name of the actual type. It is possible to provide\ntype aliases by using the `PolymorphicTypes` annotation.\n\n```java \n@Polymorphic\n@PolymorphicTypes({\n        @PolymorphicTypes.Type(type = Impl1.class, alias = \"IMPL_1\"),\n        @PolymorphicTypes.Type(type = Impl2.class, alias = \"IMPL_2\")\n})\ninterface B { ... }\n\nrecord Impl1(...) implements B { ... }\nrecord Impl2(...) implements B { ... }\n```\n\n### Custom serializers\n\nIf you want to add support for a type that is not a Java record or whose class\nis not annotated with `@Configuration`, or if you don't like how one of the\nsupported types is serialized by default, you can write your own custom\nserializer.\n\nSerializers are instances of the `de.exlll.configlib.Serializer` interface. When\nimplementing that interface you have to make sure that you convert your source\ntype into one of the valid target types listed\nin [type conversion](#type-conversion-and-serializer-selection) section.\n\nThe serializer then has to be registered through a `ConfigurationProperties`\nobject or alternatively be applied to a configuration element or type\nwith [`@SerializeWith`](#the-serializewith-annotation). If you want to use\nthe `@SerializeWith` annotation, your serializer class must either have a\nconstructor with no parameters or one with exactly one parameter of\ntype [`SerializerContext`](#the-serializercontext-interface).\n\nThe following `Serializer` serializes instances of `java.awt.Point` into strings\nand vice versa.\n\n```java\npublic final class PointSerializer implements Serializer\u003cPoint, String\u003e {\n    @Override\n    public String serialize(Point element) {\n        return element.x + \":\" + element.y;\n    }\n\n    @Override\n    public Point deserialize(String element) {\n        String[] parts = element.split(\":\");\n        int x = Integer.parseInt(parts[0]);\n        int y = Integer.parseInt(parts[1]);\n        return new Point(x, y);\n    }\n}\n```\n\n```java \nYamlConfigurationProperties properties = YamlConfigurationProperties.newBuilder()\n        .addSerializer(Point.class, new PointSerializer())\n        .build(); \n```\n\n##### The `SerializerContext` interface\n\nInstances of the `SerializerContext` interface contain contextual information\nfor custom serializers. A context object gives access to the configuration\nproperties, configuration element, and the annotated type for which the\nserializer was selected.\n\nContext objects can be obtained when adding serializer factories through\nthe `addSerializerFactory` method:\n\n```java\npublic final class PointSerializer implements Serializer\u003cPoint, String\u003e {\n    private final SerializerContext context;\n\n    public PointSerializer(SerializerContext context) {\n        this.context = context;\n    }\n    // implementation ...\n}\n```\n\n```java \nYamlConfigurationProperties properties = YamlConfigurationProperties.newBuilder()\n        .addSerializerFactory(Point.class, PointSerializer::new)\n        .build();\n```\n\nCustom serializers used with `@SerializeWith` are allowed to declare a\nconstructor with one parameter of type `SerializerContext`. If such a\nconstructor exists, a context object is passed to it when the serializer is\ninstantiated by this library.\n\n### Type coercion\n\nWhen deserializing the value for a configuration element, it can happen (e.g.\ndue to misconfiguration) that the value the deserializer receives is of the\nwrong type. For example, it can happen that the configuration element is of type\n`String` but the value the deserializer receives is of type `Number` because\nsomeone forgot to wrap the value in quotes `\"` in the configuration file. In\nsuch cases, an exception is thrown.\nHowever, if these misconfigurations are excepted to happen often, or you simply\nprefer the validation logic of this library to be less strict, you can configure\nthis library to perform several conversions of normally incompatible types\nautomatically.\n\nBy default, this library converts all number types into one another (e.g. `long`\nto `double`, etc.) and this cannot be disabled. All other conversions are off by\ndefault and must be allowed explicitly by configuring a `ConfigurationProperties`\nobject.\n\n```java\nYamlConfigurationProperties.newBuilder()\n        // enable specific type coercions (varargs argument):\n        .setDeserializationCoercionTypes(BOOLEAN_TO_STRING, NUMBER_TO_STRING)\n        // or, if you want to enable all type coercions\n        //  (taking the risk that there might be added more in the future):\n        .setDeserializationCoercionTypes(DeserializationCoercionType.values())\n        .build();\n```\n\n### Post-processing\n\nThere are two ways to apply some post-processing to your configurations:\n\n- The first is to annotate a method in your configuration type with the\n  `@PostProcess` annotation.\n- The second is to add post-processor functions to a `ConfigurationProperties`\n  object. These functions are then applied to some set of configuration elements\n  that is defined by a `ConfigurationElementFilter`.\n\nBoth ways of post-processing can be applied at the same time. In this case,\nthe post-processor functions added to a `ConfigurationProperties` object run\nfirst.\n\n#### Post-process configurations via annotated method\n\nOne way to apply post-processing to your configuration is to annotate some\nmethod of your configuration type with the `@PostProcess` annotation.\n\n```java\n@Configuration\npublic final class Config {\n    private int i = 10;\n    private String s = \"abc\";\n\n    @PostProcess\n    private void postProcess() {\n        this.i = this.i * 2;\n        this.s = this.s.repeat(2);\n    }\n}\n```\n\nThe return type of the `@PostProcess` method must either be `void` or the same\ntype as the type in which that method is defined. In the first case, the method\nis simply executed. In the latter case, the return value of the method replaces\nthe current instance when initializing a configuration. This is, in particular,\nuseful for Java records whose fields are final and cannot be modified.\n\n```java\npublic record Config(int i, String s) {\n    @PostProcess\n    private Config postProcess() {\n        return new Config(i * 2, s.repeat(2));\n    }\n}\n```\n\nThe name of the `@PostProcess` method can be any valid Java method name.\nHowever, your configuration type is allowed to define at most one such method\nand `@PostProcess` methods of parent classes are _not_ executed.\n\n#### Post-process configuration elements by condition\n\nThe second way to apply post-processing to your configuration is to define\na `ConfigurationElementFilter`. Such a filter implicitly defines a set of\nconfiguration elements to which some post-processing function should be applied.\nBoth, filters and post-processing functions, can be added via\nthe `ConfigurationProperties#addPostProcessor` method at the same time and the\nfunction is then applied to all configuration elements that are defined by the\nfilter.\n\nFor example, to double the values of _all_ configuration elements of type `int`,\nyou would add the following filter and post-processing function:\n\n```java\nConfigurationProperties.newBuilder()\n        .addPostProcessor(\n                // Predicate\u003c? super ConfigurationElement\u003c?\u003e\u003e filter\n                element -\u003e element.type().equals(int.class),\n                // UnaryOperator\u003c?\u003e postProcessor\n                (Integer value) -\u003e value * 2\n        )\n        .build();\n```\n\nNote that it is your responsibility to make sure that the filter only selects\nconfiguration elements whose type matches the type the post-processing function\nexpects.\n\nAlso note, that the post-processing function will be applied regardless of\nwhether a configuration file contained a value for some specific element.\nThis means that your post-processing function should properly handle `null`\ninput values if, for example, you allow the input of such values.\n\nThe `ConfigurationElementFilter` interface defines static factories to\nfacilitate the creation of common filters:\n\n```java\nConfigurationElementFilter.byType(Class\u003c?\u003e type)\nConfigurationElementFilter.byPostProcessKey(String key)\n```\n\nThe second factory creates a filter that selects all configuration elements that\nare annotated with `@PostProcess` and where the `key()` method of that\nannotation returns the given `key`.\n\nIn the following example, the values of `a` and `b` are doubled, the value\nof `c` is tripled, `d` is set to zero, and no post-processing is applied\nto `e` and `f`.\n\n```java\nrecord Config(\n        @PostProcess(key = \"double\") int a,\n        @PostProcess(key = \"double\") int b,\n        @PostProcess(key = \"tripple\") int c,\n        @PostProcess int d,\n        @PostProcess(key = \"missing processor\") int e,\n        int f\n) {}\n\nConfigurationProperties.newBuilder()\n        .addPostProcessor(\n                ConfigurationElementFilter.byPostProcessKey(\"double\"),\n                (Integer value) -\u003e value * 2\n        )\n        .addPostProcessor(\n                ConfigurationElementFilter.byPostProcessKey(\"tripple\"),\n                (Integer value) -\u003e value * 3\n        )\n        .addPostProcessor(\n                ConfigurationElementFilter.byPostProcessKey(\"\"),\n                (Integer value) -\u003e 0\n        )\n        .build();\n```\n\n### Overwriting configuration values with environment variables\n\nYou can allow users of your configuration to overwrite values that come from\nconfiguration files with values that are provided by environment variables.\nWhen a configuration is loaded, boolean values, numbers, and strings that appear\nin that configuration files can be overwritten with values that come from\nenvironment variables.\n\nTo enable support for overwriting config values with environment variables\nfirst create a `ConfigurationProperties.EnvVarResolutionConfiguration` object\nand then configure your `ConfigurationProperties` instance with it:\n\n```java\nfinal var envVarConf = ConfigurationProperties.EnvVarResolutionConfiguration\n        .resolveEnvVarsWithPrefix(prefix, caseSensitiveResolution);\nfinal var properties = ConfigurationProperties.newBuilder()\n        .setEnvVarResolutionConfiguration(envVarConf)\n        // ... build\n```\n\nThe `resolveEnvVarsWithPrefix` method returns a configuration object that\nresolves environment variables that start with the given prefix.\n\nIf you specify a non-empty prefix, then only variables that start with that\nprefix will be resolved when loading your configuration. For example, if you\nchoose `MY_PREFIX_` as your prefix, only environment variables that start\nwith `MY_PREFIX_` will be able to overwrite configuration values. Specifying a\nnon-empty prefix that is specific to your project is highly recommended.\n\n#### Environment variable names\n\nTo be able to overwrite configuration values, you need to name your environment\nvariables in a specific way (see example below):\n\n- To overwrite a boolean, number, or string value of a field, use an environment\nvariable whose name matches the name of the field. The environment variable must\nbe uppercase if case-sensitive resolution is disabled.\n- You can target nested elements by joining all field names that lead to that\nelement with an underscore `_`.\n- You can target list elements by using the index of an element as the field name.\n\nFor example, in the following configuration file\n\n```yaml\na: 0\nb:\n  c: 1\nd:\n- 2\n- 3\ne:\n- f: 4\n  g: 5\n```\n\n`a` is a simple value, `b` is a map, `d` is a list, and `e` is a list of\nmaps. Then, under the assumption that you aren't using a prefix and that\ncase-sensitive resolution is disabled, you can overwrite the values from 0 to 5\nusing the following environment variables, respectively:\n`A`, `B_C`, `D_0`, `D_1`, `E_0_F`, and `E_0_G`.\n\n#### Case-sensitivity\n\n**NOTE:** Environment variables are case-sensitive on UNIX systems and\ncase-insensitive on Windows.\n\nIf you want to (or have to, because you are on Windows) target the values of\nyour configuration using uppercase environment variables (irrespective of the\nactual casing of the name of the fields), then you have to disable\ncase-sensitive resolution. For example, if you have a configuration file with\nthe following content\n```yaml\nalPHA:\n  be_ta: 1\nga_mma:\n  dELTa: 2\n```\nthen, with `caseSensitiveResolution` set to `false`, you can overwrite the values\n`1` and `2` using the environment variables `ALPHA_BE_TA` and `GA_MMA_DELTA`,\nrespectively.\n\nIf your configuration contains paths that differ only in their case but are\notherwise the same, and if you want to target these paths individually, then\nyou have to enable case-sensitive resolution. For example, if you have a\nconfiguration file with the following content\n```yaml\nalpha:\n  beta: 1\nALPHA:\n  BETA: 2\n```\nand you want to target the values of `beta` and `BETA` individually, then you\nhave to set `caseSensitiveResolution` to `true`. After doing so you can\noverwrite the values `1` and `2` by using the environment variables `alpha_beta`\nand `ALPHA_BETA`, respectively.\n\n#### Restrictions\n\nThe values provided by environment variables come as strings and need to be\nconverted to the correct target type. Because the resolution of environment\nvariables happens very early in the process (before the type information of the\nconfiguration type is available) there are a few restrictions on how\nenvironment variables are resolved and which values can be overwritten:\n\n- Environment variables can only be used to overwrite simple values.\n  Trying to overwrite a collection or map will cause an exception.\n- Environment variables cannot be used to insert new values into collections\n  or maps.\n- Overwriting `null` values is not possible because `null`s don't carry any type\n  information.\n- To overwrite a boolean value, an environment variable must be assigned the\n  string `true` or `false` (using any casing).\n- Numbers that, when a configuration file is read, are interpreted as integers\n  (because they don't contain a decimal point), (currently) cannot be overwritten\n  by floating point numbers. This applies even if the configuration type actually\n  expects a floating point number.\n\n### Changing the type of configuration elements\n\nChanging the type of configuration elements is not supported. If you change the\ntype of one of these but your configuration file still contains a value of the\nold type, a type mismatch will occur when loading a configuration from that\nfile. Instead, remove the old element and add a new one with a different name.\n\n### Recursive type definitions\n\nRecursive type definitions are currently not allowed but might be supported in a\nfuture version if this feature is requested.\n\n\u003cdetails\u003e\n \u003csummary\u003eExamples of recursive type definitions\u003c/summary\u003e\n\nNeither direct nor indirect recursive type definitions are supported.\n\n```java\npublic final class RecursiveTypDefinitions {\n    // Direct recursive definition\n    @Configuration\n    static final class R {\n        R r;\n    }\n\n    // Indirect recursive definition\n    @Configuration\n    static final class R1 {\n        R2 r2;\n    }\n\n    @Configuration\n    static final class R2 {\n        R1 r1;\n    }\n}\n```\n\n\u003c/details\u003e\n\n## Project and repository structure\n\nThis project contains three classes of modules:\n\n* The `configlib-core` module contains most of the logic of this library. In it,\n  you can find (among other things), the object mapper that converts\n  configuration instances to maps (and vice versa), most serializers, and the\n  classes responsible for the extraction of comments. It does not contain\n  anything Minecraft related.\n* The `configlib-yaml` module contains the classes that can save configuration\n  instances as YAML files and instantiate new instances from such files. This\n  module does not contain anything Minecraft related, either.\n* The `configlib-paper`, `configlib-velocity`, and `configlib-waterfall` modules\n  contain basic plugins that are used to conveniently load this library. These\n  three modules shade the `-core` module, the `-yaml` module, and the YAML\n  parser when the `shadowJar` task is executed. The shaded jar files are\n  released on the [releases page](https://github.com/Exlll/ConfigLib/releases).\n    * The `configlib-paper` module additionally contains\n      the `ConfigLib.BUKKIT_DEFAULT_PROPERTIES` object which adds support for\n      the serialization of Bukkit classes like `ItemStack` as\n      described [here](#support-for-bukkit-classes-like-itemstack).\n\nThe GitHub repository of this project uses two branches:\n\n* The `master` branch contains the functionality of the latest release version.\n* The `dev` branch contains the newest, possibly unstable features and\n  refactorings.\n\n**If you plan to contribute to this project, please base your commits on\nthe `dev` branch.**\n\n## Import\n\nTo use this library, import it into your project with Maven or Gradle. Examples\nof how to do that are at the end of this section within the spoilers. Currently,\nthere are three repositories from which you can choose:\n- [Maven Central](https://central.sonatype.com/search?q=de.exlll) (recommended),\n- [jitpack.io](https://jitpack.io/#Exlll/ConfigLib), and\n- GitHub (which [requires authentication](https://github.com/Exlll/ConfigLib/issues/12))\n\nThis library has additional dependencies (namely, a YAML parser) which are not\nexposed by the artifact you import. You can download _plugin versions_ of this\nlibrary that bundle all its dependencies. The artifacts of these versions can be\nfound on the [releases page](https://github.com/Exlll/ConfigLib/releases) where\nyou can identify them by their `-paper-`, `-waterfall-`, and `-velocity-` infix\nand `-all` suffix. Except for the `-paper-` version, the other plugin versions\ncurrently do not add any additional features. A benefit of these versions is\nthat they make it easier for you to update this library if you have written\nmultiple plugins that use it. If you plan to use these versions, don't forget to\nadd the plugin as a dependency to the `plugin.yml` (for Paper and Waterfall) or\nto the dependencies array (for Velocity) of your own plugin.\n\nAlternatively, if you don't want to use an extra plugin, you can shade\nthe `-yaml` version with its YAML parser yourself.\n\n### Import examples\n\nIf you want serialization support for Bukkit classes like `ItemStack`,\nreplace `configlib-yaml` with `configlib-paper`\n(see [here](#support-for-bukkit-classes-like-itemstack)).\n\n#### Import via Maven Central\n\n**Maven**\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ede.exlll\u003c/groupId\u003e\n    \u003cartifactId\u003econfiglib-yaml\u003c/artifactId\u003e\n    \u003cversion\u003e4.8.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n**Gradle**\n\n```kotlin\nrepositories { mavenCentral() }\n\ndependencies { implementation(\"de.exlll:configlib-yaml:4.8.0\") }\n```\n\n\u003cdetails\u003e\n \u003csummary\u003e\n    Import via \u003ca href=\"https://jitpack.io/#Exlll/ConfigLib\"\u003ejitpack.io\u003c/a\u003e\n \u003c/summary\u003e\n\n**Maven**\n\n```xml \n\u003crepository\u003e\n    \u003cid\u003ejitpack.io\u003c/id\u003e\n    \u003curl\u003ehttps://jitpack.io\u003c/url\u003e\n\u003c/repository\u003e\n\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.github.Exlll.ConfigLib\u003c/groupId\u003e\n    \u003cartifactId\u003econfiglib-yaml\u003c/artifactId\u003e\n    \u003cversion\u003ev4.8.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n**Gradle**\n\n```groovy\nrepositories { maven { url 'https://jitpack.io' } }\n\ndependencies { implementation 'com.github.Exlll.ConfigLib:configlib-yaml:v4.8.0' }\n```\n\n```kotlin\nrepositories { maven { url = uri(\"https://jitpack.io\") } }\n\ndependencies { implementation(\"com.github.Exlll.ConfigLib:configlib-yaml:v4.8.0\") }\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n \u003csummary\u003e\n    Import via GitHub\n \u003c/summary\u003e\n\nImporting via GitHub requires authentication. Check\nthis [issue](https://github.com/Exlll/ConfigLib/issues/12) if you have any\ntrouble with that.\n\n**Maven**\n\n```xml \n\u003crepository\u003e\n    \u003cid\u003ede.exlll\u003c/id\u003e\n    \u003curl\u003ehttps://maven.pkg.github.com/Exlll/ConfigLib\u003c/url\u003e\n\u003c/repository\u003e\n\n\u003cdependency\u003e\n    \u003cgroupId\u003ede.exlll\u003c/groupId\u003e\n    \u003cartifactId\u003econfiglib-yaml\u003c/artifactId\u003e\n    \u003cversion\u003e4.8.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n**Gradle**\n\n```groovy\nrepositories { maven { url 'https://maven.pkg.github.com/Exlll/ConfigLib' } }\n\ndependencies { implementation 'de.exlll:configlib-yaml:4.8.0' }\n```\n\n```kotlin\nrepositories { maven { url = uri(\"https://maven.pkg.github.com/Exlll/ConfigLib\") } }\n\ndependencies { implementation(\"de.exlll:configlib-yaml:4.8.0\") }\n```\n\n\u003c/details\u003e\n\n## Future work\n\nThis section contains ideas for upcoming features. If you want any of these to\nhappen any time soon,\nplease [open an issue](https://github.com/Exlll/ConfigLib/issues/new) where we\ncan discuss the details.\n\n- JSON, TOML, XML support\n- More features and control over updating/versioning\n- More control over the ordering of fields, especially in parent/child class\n  scenarios\n- Recursive definitions\n- Shadowing of fields\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fexlll%2Fconfiglib","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fexlll%2Fconfiglib","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fexlll%2Fconfiglib/lists"}