-
Notifications
You must be signed in to change notification settings - Fork 68
Description
I'd like to use RecordBuilder in combination with Jackson using the builder for deserialization in order to leverage immutable collections and default values/initializers. This should work with a vanilla ObjectMapper instance without any custom mixins/configuration.
Problem description
Starting from the following test, it fails as type and properties is null as it does not use the builder at all (expected):
package com.example.model;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.soabase.recordbuilder.core.RecordBuilder;
import org.junit.jupiter.api.Test;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
class RecordSerializationTest {
@RecordBuilder
@RecordBuilder.Options(
useImmutableCollections = true
)
// @JsonDeserialize(builder = RecordSerializationTestMyTestModelBuilder.class)
public record MyTestModel(
String name,
@RecordBuilder.Initializer("DEFAULT_TYPE") String type,
Map<String, Object> properties
) {
public static final String DEFAULT_TYPE = "dummy";
}
private final ObjectMapper objectMapper = new ObjectMapper();
@Test
void deserializingModelInvokesBuilder() throws JsonProcessingException {
final var json = """
{
"name" : "test"
}
""";
final var model = objectMapper.readValue(json, MyTestModel.class);
assertThat(model.name()).isEqualTo("test");
assertThat(model.type()).isEqualTo("dummy");
assertThat(model.properties()).isNotNull().isEmpty();
}
}When uncommenting the @JsonDeserialize line, it tries to use the builder, but fails as Jackson's default implementation expects builder methods to start with with:
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "name" (class com.example.model.RecordSerializationTestMyTestModelBuilder), not marked as ignorable (0 known properties: ])
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 2, column: 13] (through reference chain: com.example.model.RecordSerializationTestMyTestModelBuilder["name"])
A solution to reconfigure Jackson to be aware of the builder using no prefix would be to annotate the builder class with @JsonPOJOBuilder(withPrefix = ""), but either I missed it or it is currently not possible to apply this to the generated builder class.
An ugly workaround is to enable public constructors on the builder class + to extend the generated builder with an annotated one (this makes the test pass):
@RecordBuilder
@RecordBuilder.Options(
useImmutableCollections = true,
publicBuilderConstructors = true
)
@JsonDeserialize(builder = MyTestModel.AnnotatedBuilder.class)
public record MyTestModel(
String name,
@RecordBuilder.Initializer("DEFAULT_TYPE") String type,
Map<String, Object> properties
) {
public static final String DEFAULT_TYPE = "dummy";
@JsonPOJOBuilder(withPrefix = "")
public static class AnnotatedBuilder extends RecordSerializationTestMyTestModelBuilder {
}
}Possible solutions
It would be great if RecordBuilder could expose a way to make this work nice together with Jackson without any workarounds or reconfiguration of the ObjectMapper. I'm aware that this affects interoperability with a third-party library but as this is a very common use-case (e.g. deserialization in Spring Boot controllers) it would be great to have built-in Jackson support directly in RecordBuilder.
I could imagine multiple ways of doing this:
- Add an option like
addJsonPOJOBuilderAnnotationand add@JsonPOJOBuilder(withPrefix = "")to the generated builder if configured (potentially auto-enable this when Jackson is found on the classpath?). - Kind of a workaround:
provide a way to configure the setter prefix for builder methods to it can be set toI noticed this is already there, but not documented - created a small PR to update the docs in Add setterPrefix and enableGetters to options documentation #228. This works when setting it towith.withbut it would be nicer to keep the builders as they are instead of introducing the prefix. - Provide a way to specify arbitrary annotations which should be applied to the builder (given technical feasibility, as far as I've seen there have been similar discussions before).