Skip to content
zml edited this page Apr 24, 2020 · 15 revisions

Object Mapper

Basic Usage

Can only create objects that have no-arg constructor, otherwise restricted to populating already existing objects

Supported Types

  • String, float, double, int, long, byte, char
  • Maps
  • Lists
  • URIs
  • URLs
  • Objects annotated with @ConfigSerializable (which are loaded from the object mapper)
  • Enum values

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

Extending

Adding custom TypeSerializers

TypeSerializers.getDefaultSerializers().registerType(TypeToken, TypeSerializer) with custom implementations of TypeSerializer. Newly registered serializers are added with highest priority.

Points to extend ObjectMapper

collectFields(Map<String, FieldData> fields, Class<? super T> clazz): To control how fields that are settings are discovered from a class

constructObject(): To control how the object is created

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 class ObjectMapperExample {
    public static void main(String[] args) throws IOException, ObjectMappingException {
        final Path file = Path.of(args[0]);
        final GsonConfigurationLoader loader = GsonConfigurationLoader.builder()
                .setPath(file) // or setUrl(), or setFile(), or setSource/Sink
                .build();

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

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

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

@ConfigSerializable
class MyConfiguration {
    private static final ObjectMapper<MyConfiguration> MAPPER;

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

    public static MyConfiguration loadFrom(ConfigurationNode node) throws ObjectMappingException {
        return MAPPER.bindToNew().populate(node);
    }

    @Setting(value = "item-name") // The key for a setting is normally provided by the field name, but can be overridden
    private @Nullable String itemName;
    @Setting(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<>();

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

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

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

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

    public void saveTo(ConfigurationNode node) {
        MAPPER.bind(this).serialize(node);
    }
}

@ConfigSerializable
class Section {
    @Setting
    private String name;

    @Setting
    private UUID id;

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

    public UUID getId() {
        return id;
    }
}
Clone this wiki locally