I have a 3rd party Lombok builder POJO, one that I cannot modify, that I want to serialize using jackson. Notably it does not have a NoArgsConstructor.
@Data
@Builder
public class ExternalClass {
private String name;
private String data;
// etc.
}
On the surface this would appear to be simple, but it is incredibly frustrating in practice as each possible option seems to be counteracted by a different complication. In essence, I'm having trouble getting an external Lombok builder to work with a jackson mixin.
Lombok produces fluent setters of the style .name(String name)
while Jackson's built-in builder deserializer expects .withName(String name)
. Lombok documentation, and recipes elsewhere such as here suggest using @JsonDeserialize(builder=ExternalClass.ExternalClassBuilder.class)
in conjunction with @JsonPOJOBuilder(withPrefix="")
on a predeclared inner stub builder. But this is not possible because the Lombok class is in an external library.
Applying these annotations to a mixin has no effect.
@JsonDeserialize(ExternalClass.ExternalClassBuilder.class)
public abstract class ExternalClassMixin {
@JsonPOJOBuilder(withPrefix="")
public static ExternalClassBuilder {
}
}
The only approach I've found that works is to leverage the package-access AllArgsConstructor created by @Builder
and populate the mixin with the following constructor
public abstract class ExternalClassMixin {
@JsonCreator public ExternalClassMixin(
@JsonProperty("name") String name,
@JsonProperty("data") String data,
// etc.
) {}
}
This is obviously not desirable as it requires iterating and hard-coding every class property explicitly, making the mixin fragile to any change in the external POJO.
My question is - is there a robust, maintainable way to serialize this external builder class using Jackson without modifying it, using either a mixin or maybe a full blown deserializer?
Update
I implemented the excellent answer by @jan-rieke, including the suggestion to use reflection to seek out the inner builder class.
...
public Class<?> findPOJOBuilder(AnnotatedClass ac) {
Class<?> innerBuilder;
try {
innerBuilder = Class.forName(ac.getName()+"$"+ac.getRawType().getSimpleName()+"Builder");
log.info("Builder found: {}", ac.getName());
return innerBuilder;
} catch( ClassNotFoundException e ) {
return super.findPOJOBuilder(ac);
}
}
You can customize your ObjectMapper
as follows:
ObjectMapper mapper = new ObjectMapper();
mapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector() {
@Override
public Class<?> findPOJOBuilder(AnnotatedClass ac) {
if (ExternalClass.class.equals(ac.getRawType())) {
return ExternalClass.ExternalClassBuilder.class;
}
return super.findPOJOBuilder(ac);
}
@Override
public Value findPOJOBuilderConfig(AnnotatedClass ac) {
if (ac.hasAnnotation(JsonPOJOBuilder.class)) {
return super.findPOJOBuilderConfig(ac);
}
return new JsonPOJOBuilder.Value("build", "");
}
});
This will
ExternalClass
uses its builder, and""
(except when the @JsonPOJOBuilder
annotation is present).If you do not want to list all external classes explicitly in findPOJOBuilder()
, you can of course programmatically look into the class to check whether it has a inner class that looks like a builder.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With