=================== Serializing Objects =================== .. javadoc-import:: com.google.common.reflect.TypeToken java.util.List java.util.Map java.util.Set java.util.UUID java.net.URL java.net.URI java.util.regex.Pattern ninja.leaping.configurate.ConfigurationOptions ninja.leaping.configurate.objectmapping.GuiceObjectMapperFactory ninja.leaping.configurate.objectmapping.ObjectMapper ninja.leaping.configurate.objectmapping.ObjectMapperFactory ninja.leaping.configurate.objectmapping.Setting ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable ninja.leaping.configurate.objectmapping.serialize.Scalars ninja.leaping.configurate.objectmapping.serialize.TypeSerializer ninja.leaping.configurate.objectmapping.serialize.TypeSerializerCollection org.spongepowered.api.text.format.TextFormat org.spongepowered.api.util.TypeTokens The Configurate library also provides the means to tweak automatic serialization and deserialization of objects. Per default, a set of data types can be (de)serialized: * ``String``\s, the most commonly used primitive types and their wrappers * :javadoc:`List`\s, :javadoc:`Set`\s, and arrays of serializable values (not including specific implementations) * :javadoc:`Map`\s where both keys and values are serializable (not including specific implementations) * The types :javadoc:`UUID`, :javadoc:`URL`, :javadoc:`URI` and :javadoc:`Pattern {(regex) Pattern}` * Any other :javadoc:`Scalars {scalar type}` * Any enum or :doc:`CatalogType ` * The types :doc:`Text `, :javadoc:`TextFormat` and :doc:`TextTemplate ` (See also :doc:`here `) .. note:: If you need special constraints or rules for your serialization (such as sorting the elements in a ``Set``), then you should consider using your own ``TypeSerializer`` implementations. Configurate provides several abstract types. But if you want to write your custom data structures to a config file, this may not be enough. Imagine a data structure tracking how many diamonds a player has mined. It might look a little like this: .. code-block:: java public class DiamondCounter { private UUID playerUUID; private int diamonds; [...] } Also assume some methods to access those fields, a nice constructor setting both of those etc. Creating a Custom TypeSerializer ================================ The first way you might think of writing and loading such a data structure is providing a custom :javadoc:`TypeSerializer`. The ``TypeSerializer`` interface provides two methods, one to write the data from an object to a configuration node and one to create an object from a given configuration node. .. code-block:: java import com.google.common.reflect.TypeToken; import ninja.leaping.configurate.objectmapping.ObjectMappingException; import ninja.leaping.configurate.objectmapping.serialize.TypeSerializer; public class DiamondCounterSerializer implements TypeSerializer { public static final TypeToken TYPE = TypeToken.of(DiamondCounter.class); @Override public DiamondCounter deserialize(TypeToken type, ConfigurationNode value) throws ObjectMappingException { UUID player = value.getNode("player").getValue(TypeToken.of(UUID.class)); int diamonds = value.getNode("diamonds").getInt(); return new DiamondCounter(player, diamonds); } @Override public void serialize(TypeToken type, DiamondCounter obj, ConfigurationNode value) throws ObjectMappingException { value.getNode("player").setValue(obj.getPlayerUUID()); value.getNode("diamonds").setValue(obj.getDiamonds()); } } This ``TypeSerializer`` must then be registered with Configurate. This can be done by specifying it in the :javadoc:`ConfigurationOptions` when loading your config. In the past, global registration has been supported, but .. note:: ``ConfigurationOptions`` are immutable. Every time you try to modify the original instance a new instance is created; so you either have to use the (chained) result directly or update your variable accordingly. **Code Example: Registering a TypeSerializer** .. code-block:: java import ninja.leaping.configurate.ConfigurationNode; import ninja.leaping.configurate.ConfigurationOptions; import ninja.leaping.configurate.objectmapping.serialize.TypeSerializerCollection; import ninja.leaping.configurate.objectmapping.serialize.TypeSerializers; ConfigurationOptions options = someConfigurationLoader.getDefaultOptions().withSerializers(collection -> { collection.registerType(DiamondCounterSerializer.TYPE, new DiamondCounterSerializer()); }); ConfigurationNode rootNode = someConfigurationLoader.load(options); .. tip:: Generally, serializers should provide their :javadoc:`TypeToken` as a constant so that it is easily available for any users who may want to register an instance. Using ObjectMappers =================== Since in many cases the (de)serialization boils down to mapping fields to configuration nodes, writing such a ``TypeSerializer`` is a rather dull affair and something we'd like Configurate to do on its own. So let's annotate our class with the :javadoc:`ConfigSerializable` and :javadoc:`Setting` annotations. .. code-block:: java import ninja.leaping.configurate.objectmapping.Setting; import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable; @ConfigSerializable public class DiamondCounter { @Setting(value="player", comment="Player UUID") private UUID playerUUID; @Setting(comment="Number of diamonds mined") private int diamonds; [...] } The above example can now be serialized and deserialized from config nodes without further registration. The ``@Setting`` annotations map a configuration node to the field that was annotated. It accepts two optional parameters, ``value`` and ``comment``. If the ``value`` parameter exists, it defines the name of the node the field will be saved in. If it is not present, the name of the field will be used instead. So in our above example, the annotation ensures that the contents of the field ``playerUUID`` are saved to the node "player", commented with "Player UUID". The ``diamonds`` field however will be saved under that exact name since its annotation only specifies a comment. That comment will be written to the config if the implementation supports commented configuration nodes, otherwise it will be discarded. .. tip:: You may also use the shorthand ``@Setting("someNode")`` instead of ``@Setting(value="someNode")`` The ``@ConfigSerializable`` annotation eliminates the need for any registration since it allows Configurate to just generate an :javadoc:`ObjectMapper` for the class. The only limitation is that Configurate needs an empty constructor to instantiate a new object before filling in the annotated fields. .. note:: You can also have fields that are not are not annotated with ``@Setting`` in your ``@ConfigSerializable`` classes. These fields won't be persisted to config files and can be used to store temporary references for your plugin. For better compatility with future versions of the ObjectMapper, these changes should be Using Default Values in ConfigSerializable Types ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ It is also possible to use default values inside of ``@ConfigSerializable`` types. You just have to use Java's field initializers (or getters) to set some default values. Any default values set will override null values in the configuration being loaded from. .. tip:: Starting from Configurate 4.0, ``ObjectMapper`` defaults will only be written to the underlying node if the :javadoc:`ConfigurationOptions#shouldCopyDefaults()` option has been set to ``true``. To ease migration, this value should be set even in v3.7. .. code-block:: java @ConfigSerializable public class DiamondCounter { @Setting(value="player", comment="Player UUID") private UUID playerUUID; @Setting(comment="Number of diamonds mined") private int diamonds = 0; @Setting(comment="The time the player found a diamond last.") private LocalDateTime diamonds = LocalDateTime.now(); [...] } Example: Loading a ConfigSerializable Config with Default Values ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Instead of loading a default config from the plugin jar itself, it is also possible to just ask Configurate to create it if it is missing. .. code-block:: java try { this.config = this.configManager.load().getValue(Configuration.TYPE, Configuration::generateDefault); } catch (ObjectMappingException | IOException e) { this.logger.error("Failed to load the config - Using a default", e); this.config = Configuration.generateErrorDefault(); } In this case you load the entire configuration into a ``Configuration`` object that contains all of your plugins configuration. Using such a class has the following benefits: * Type safety is guaranteed * No need to update the configuration file shipped in your plugin * You don't need to store lots of references for each of your configuration options * You can pass this config (or its parts) into methods or reference it from other classes * It is easy to write comments for each attribute in a place that also helps you during development .. note:: In this case ``Configuration.generateDefault()`` is called when the config file is missing or empty. If you still want to load the shipped default config asset you can load it inside of that method. ``Configuration.generateErrorDefault()`` is called when there is an error reading or parsing the config. It is not necessary to use separate methods for those cases; you can also use the no-arg constructor, or use an entirely custom solution. Example: Saving a ConfigSerializable Config ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Saving a ``@ConfigSerializable`` config is also very simple, as shown by the following example: .. code-block:: java try { this.configManager.save(this.configManager.createEmptyNode().setValue(Configuration.TYPE, this.config)); } catch (IOException | ObjectMappingException e) { this.logger.error("Failed to save the config", e); } Providing a custom ObjectMapperFactory ====================================== While normally an object mapper can only construct instances of objects with empty constructors, an :javadoc:`ObjectMapperFactory`, for example a :javadoc:`GuiceObjectMapperFactory` can provide other methods for object construction.. Instead of requiring an empty constructor, it will work on any class that guice can create via dependency injection. This also allows for a mixture of ``@Inject`` and ``@Setting`` annotated fields. Any ``ConfigurationLoader`` provided by Sponge will use the Guice ``ObjectMapperFactory`` to construct any instances of objects created using the ``getValue(TypeToken)`` method on ConfigurationNode. For more complicated scenarios, the :javadoc:`GuiceObjectMapperFactory` is available through the plugin`s ``Injector`` (see :doc:`../injection`) directly. .. code-block:: java import com.google.inject.Inject; import java.nio.file.Path; import ninja.leaping.configurate.commented.CommentedConfigurationNode; import ninja.leaping.configurate.loader.ConfigurationLoader; import ninja.leaping.configurate.objectmapping.GuiceObjectMapperFactory; import org.spongepowered.api.event.Listener; import org.spongepowered.api.event.game.state.GamePreInitializationEvent; import org.spongepowered.api.plugin.Plugin; @Plugin(name="IStoleThisFromZml", id="shamelesslystolen", version="0.8.15", description = "Stolen") public class StolenCodeExample { @Inject private GuiceObjectMapperFactory factory; @Inject private @ConfigDir(sharedRoot=true) Path configBase; @Listener public void enable(final GamePreInitializationEvent event) throws IOException, ObjectMappingException { final Path specificPath = configBase.resolve("mangos.conf"); final HoconConfigurationLoader loader = HoconConfigurationLoader.builder() .setDefaultOptions(o -> o.withObjectMapperFactory(factory)) .build(); CommentedConfigurationNode node = loader.load(); DiamondCounter myDiamonds = node.getValue(TypeToken.of(DiamondCounter.class)); } } .. note:: The above code is an example and, for brevity, lacks proper error handling.