Polymorphic deserialization in Jackson without annotations

Razor :

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).

cassiomolin :

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 that target (or its supertypes) has.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=145098&siteId=1