Skip to content
SizableShrimp edited this page Nov 15, 2020 · 15 revisions

Object Mapper

Basic Usage

Can only create objects that have no-arg constructor, otherwise restricted to populating already existing objects. Object mappers will populate all fields annotated with @Setting, using the type of a field to determine which type serializer to use. As of v3.7, parameterized object-mappable classes can be used, and their type parameters will be resolved in fields.

Supported Types

String | float | double | int | long | byte | ConfigurationNode | char | Map | List | Set | URL | @ConfigSerializable objects | boolean | UUID | Pattern | any array | URI | enum class values | short | Path | File

Other supported types may be added by registering custom TypeSerializers, explained below.

Extending

Adding custom TypeSerializers

Create a new child TypeSerializer, which can be used when creating a node:

final TypeSerializerCollection own = TypeSerializerCollection.defaults().childBuilder()
        .register(MyCustomType.class, MyCustomTypeSerializer.INSTANCE)
        .build();

This can be passed through to a configuration loader as well. Here is an example with the YAML loader.

final YamlConfigurationLoader loader = YamlConfigurationLoader.builder()
        .defaultOptions(o -> o.serializers(own))
        .path(Paths.get("config.yml"))
        .build()

Extending the object mapper

As of version 4.0, the object mapper has defined extension points, set up on an ObjectMapper.Factory.Builder. See their class documentation for details on what each one does.

Bringing it all together

Here's a example of a standalone ObjectMapper setup. This uses a few value types, some nodes, and shows that type parameters are interpreted where specified.

public final class ObjectMapperExample {

    private ObjectMapperExample() {}

    public static void main(final String[] args) throws ConfigurateException {
        final Path file = Paths.get(args[0]);
        final HoconConfigurationLoader loader = HoconConfigurationLoader.builder()
                .defaultOptions(opts -> opts.shouldCopyDefaults(true))
                .path(file) // or setUrl(), or setFile(), or setSource/Sink
                .build();

        final CommentedConfigurationNode node = loader.load(); // Load from file
        final MyConfiguration config = MyConfiguration.loadFrom(node); // Populate object

        // Do whatever actions with the configuration, then...
        config.itemName("Steve");

        config.saveTo(node); // Update the backing node
        loader.save(node); // Write to the original file
    }

    @ConfigSerializable
    static class MyConfiguration {

        private static final ObjectMapper<MyConfiguration> MAPPER;

        static {
            try {
                MAPPER = ObjectMapper.factory().get(MyConfiguration.class); // We hold on to the instance of our ObjectMapper
            } catch (final SerializationException e) {
                throw new ExceptionInInitializerError(e);
            }
        }

        public static MyConfiguration loadFrom(final ConfigurationNode node) throws SerializationException {
            return MAPPER.load(node);
        }

        private @Nullable String itemName;

        @Comment("Here is a comment to describe the purpose of this field")
        private Pattern filter = Pattern.compile("cars?"); // Set defaults by initializing the field

        // As long as custom classes are annotated with @ConfigSerializable, they can be nested as ordinary fields.
        private List<Section> sections = new ArrayList<>();

        // This won't be written to the file because it's marked as `transient`
        private transient @MonotonicNonNull String decoratedName;

        public @Nullable String itemName() {
            return this.itemName;
        }

        public void itemName(final String itemName) {
            this.itemName = requireNonNull(itemName, "itemName");
        }

        public Pattern filter() {
            return this.filter;
        }

        public List<Section> sections() {
            return this.sections;
        }

        public String decoratedItemName() {
            if (this.decoratedName == null) {
                this.decoratedName = "[" + this.itemName + "]";
            }
            return this.decoratedName;
        }

        public <N extends ScopedConfigurationNode<N>> void saveTo(final N node) throws SerializationException {
            MAPPER.save(this, node);
        }

    }

    @ConfigSerializable
    static class Section {

        private String name;
        private UUID id;

        // the ObjectMapper resolves settings based on fields -- these methods are provided as a convenience
        public String name() {
            return this.name;
        }

        public UUID id() {
            return this.id;
        }

    }

}
Clone this wiki locally