I have a CloudEvent<T>
class that uses polymorphic deserialization using Jackson (2.9.0 - last version) like this:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CloudEvent<T> {
@NonNull
private String eventType;
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
property = "eventType",
defaultImpl = Void.class)
@JsonSubTypes({
@JsonSubTypes.Type(value = MyEvent1.class, name = "event-1"),
@JsonSubTypes.Type(value = MyEvent2.class, name = "event-2")
})
private T data;
}
And then deserialized with:
String cloudEventJson1 = "{\"eventType\":\"event-1\",\"data\":{\"id\":\"123\",\"details\":\"detail1\"}}";
CloudEvent deserializedEvent1 = objectMapper.readValue(cloudEventJson1, CloudEvent.class); //without subtypes
All this works fine. But because some limitations, I can not use annotations on the CloudEvent class (provided by external dependency).
So I configured the ObjectMapper like this:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerSubtypes(new NamedType(MyEvent1.class, "event-1"));
objectMapper.registerSubtypes(new NamedType(MyEvent2.class, "event-2"));
TypeResolverBuilder typeResolverBuilder = new ObjectMapper.DefaultTypeResolverBuilder(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE)
.init(JsonTypeInfo.Id.NAME, null) //CLASS works
.inclusion(JsonTypeInfo.As.EXTERNAL_PROPERTY)
.typeProperty("eventType")
.typeIdVisibility(true)
// .defaultImpl(Void.class);
objectMapper.setDefaultTyping(typeResolverBuilder);
But deserializing with same method as above does not work. It is reading the eventType
but it is not managing to match to the registered subtype.
I can not use generics or TypeReferance in the deserialization because I need to use spring-integration
for reading the events which only accepts main class; the pattern matching being done manually after deserialization.
Exception:
com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve type id 'event-1' as a subtype of [simple type, class java.lang.Object]: known type ids = [] (for POJO property 'data')
at [Source: (String)"{"eventType":"event-1","data":{"id":"123","details":"detail1"}}"; line: 1, column: 271]
Also this is configuring the ObjectMapper for all input classes. Is it possible to connect this typeResolverBuilder
and subtypes
to the CloudEvent.class
(like the annotation way does it).
A polymorphic deserialization allows a JSON payload to be deserialized into one of the known gadget classes that are documented in SubTypeValidator. java in jackson-databind in GitHub. The deserialized object is assigned to a generic base class in your object model, such as java. lang. Object or java.
Serialization is the process of converting the state of an object to a byte stream in a way that the byte stream can be reverted into a copy of the original object. Deserialization, as you may already have inferred, is the process of converting the serialized form of an object back into a copy of the original object.
ObjectMapper is the main actor class of Jackson library. ObjectMapper class ObjectMapper provides functionality for reading and writing JSON, either to and from basic POJOs (Plain Old Java Objects), or to and from a general-purpose JSON Tree Model (JsonNode), as well as related functionality for performing conversions.
Jackson by default uses the getters for serializing and setters for deserializing.
You can still rely on annotations even if you cannot modify your class. Jackson supports a feature called mix-ins: you can think of it as kind of aspect-oriented way of adding more annotations during runtime, to augment statically defined ones.
First define an interface as follows:
public interface CloudEventMixIn<T> {
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
property = "eventType",
defaultImpl = Void.class)
@JsonSubTypes({
@JsonSubTypes.Type(value = MyEvent1.class, name = "event-1"),
@JsonSubTypes.Type(value = MyEvent2.class, name = "event-2")
})
public T getData();
}
Then configure ObjectMapper
to use the defined interface as a mix-in for actual class/interface:
ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(CloudEvent.class, CloudEventMixIn.class);
From the addMixIn(Class<?> target, Class<?> mixinSource)
method documentation:
Method to use for adding mix-in annotations to use for augmenting specified class or interface. All annotations from
mixinSource
are taken to override annotations thattarget
(or its supertypes) has.
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