Jackson has a feature called Mix-in annotations. With this feature, we can write cleaner code for the domain classes. Imagine we have domain classes like below.
// package com.example
public interface Item {
ItemType getType();
}
@Value
public class FooItem implements Item{
@NonNull String fooId;
@Override
public ItemType getType() {
return ItemType.FOO;
}
}
When implement these domain classes, no Jackson annotation is put on them.
To support serialization and deserialization with Jackson for these classes, add “Mix-in” classes, for example in a
separate package called com.example.jackson
.
Add ItemMixin
below to let Jackson be able to serialize and deserialize Item
and its subclasses.
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = FooItem.class, name = "FOO")
// ...
})
public abstract class ItemMixin {
}
// merge the Jackson annotations in the Mix-in class into the Item class,
// as if these annotations are in the Item class
objectMapper.addMixIn(Item.class, ItemMixin.class);
String json = "[{\"fooId\": \"1\", \"type\": \"FOO\"}]";
List<Item> items = mapper.readValue(json, new TypeReference<List<Item>>(){});
Note that FooItem
is implemented as an immutable class using @Value
from Lombok.
With @Value
annotated, FooItem
has no default constructor, which makes Jackson unable to serialize it by default.
Add FooItemMixin
below to fix it.
@JsonIgnoreProperties(value={"type"}, allowGetters=true)
abstract class FooItemMixin {
@JsonCreator
public FooItemMixin(@JsonProperty("fooId") String fooId) {
}
}
With help of Mix-in annotations, the domain classes don’t have to be compromised for Jackson support. All Jackson relevant annotations are in separate Mix-in classes in a separate package. Further, we could provide a simple Jackson module like below.
@Slf4j
public class MixinModule extends SimpleModule {
@Override
public void setupModule(SetupContext context) {
context.setMixInAnnotations(Item.class, ItemMixin.class);
context.setMixInAnnotations(FooItem.class, FooItemMixin.class);
log.info("module set up");
}
}
The consumers of the domain classes can simple register this module to take in all the Mix-in annotations.
objectMapper.registerModule(new MixinModule());
Jackson Mix-in helps especially if the domain classes are from a third party library. In this case, the source of the domain classes cannot be modified, using Mix-in is more elegant than writing custom serializers and deserializers.