Custom Serialization
Use custom serialization when a config value should map to an application type that is not covered by the built-in scalar and object mapper.
Typed Config Serializers
For annotation configs, register a ConfigSerializer<T> in ConfigMapperOptions.
record Endpoint(String host, int port) {
}
final class EndpointSerializer implements ConfigSerializer<Endpoint> {
@Override
public ConfigNode encode(Endpoint value, ConfigSerializationContext context) {
return ConfigNode.scalar(value.host() + ":" + value.port());
}
@Override
public Endpoint decode(ConfigNode node, ConfigSerializationContext context) {
var parts = node.asString().orElseThrow().split(":", 2);
return new Endpoint(parts[0], Integer.parseInt(parts[1]));
}
}
var options = ConfigMapperOptions.builder()
.serializer(Endpoint.class, new EndpointSerializer())
.build();
var mapper = new AnnotatedConfigMapper(options);
The serializer receives a context so it can delegate nested values back to the mapper when needed.
Serializer Annotations
Use @ConfigSerializeWith when one member needs a serializer without changing every use of the type.
record ServerConfig(
@ConfigSerializeWith(EndpointSerializer.class)
Endpoint endpoint
) {
}
For collection elements, set nesting.
record ServerConfig(
@ConfigSerializeWith(value = EndpointSerializer.class, nesting = 1)
List<Endpoint> endpoints
) {
}
Static Field Codecs
pistonconfig-static-fields still uses ConfigCodecRegistry. Register a ConfigCodec<T> when a static ConfigProperty<T> needs an application type.
var codecs = new ConfigCodecRegistry()
.register(Endpoint.class, new ConfigCodec<Endpoint>() {
@Override
public ConfigNode encode(Endpoint value, ConfigCodecRegistry registry) {
return ConfigNode.object()
.set(ConfigPath.of("host"), value.host())
.set(ConfigPath.of("port"), value.port());
}
@Override
public Endpoint decode(ConfigNode node, ConfigCodecRegistry registry) {
var host = node.find(ConfigPath.of("host")).flatMap(ConfigNode::asString).orElseThrow();
var port = node.find(ConfigPath.of("port")).flatMap(ConfigNode::asInt).orElseThrow();
return new Endpoint(host, port);
}
});
Error Handling
Throw ConfigException when a node has the wrong shape or a scalar value cannot be parsed. That keeps format errors, mapping errors, and application validation errors under the same exception type.