cloudevents / sdk-java

Java SDK for CloudEvents
https://cloudevents.github.io/sdk-java/
Apache License 2.0
400 stars 160 forks source link

Polymorphic JSON deserialization with Jackson #666

Open mbechto opened 2 months ago

mbechto commented 2 months ago

I was wondering how to write a polymorphic parser for JSON CloudEvents, where the payload can be one of many types. Normally, with plain Jackson one can do something like this, using @JsonTypeInfo and a type field somewhere in the JSON to determine the specific class for deserialization.

In this simple example in the CloudEvent SDK docs we knew the type in advance:

PojoCloudEventData<User> cloudEventData = mapData(
    inputEvent,
    PojoCloudEventDataMapper.from(objectMapper,User.class)
);

But in my case, I would like to achieve something like the following:

@JsonTypeInfo(
    use = JsonTypeInfo.Id.NAME,
    property = "type"
)
@JsonSubTypes({
    @JsonSubTypes.Type(value = Foo.class, name = "foo"),
    @JsonSubTypes.Type(value = Bar.class, name = "bar")
})
sealed interface Payload permits Foo, Bar {}

record Foo(String str) implements Payload {}
record Bar(int i) implements Payload {}

// ...

PojoCloudEventData<Payload> cloudEventData = mapData(
    inputEvent,
    PojoCloudEventDataMapper.from(objectMapper, Payload.class)
);

Running this code, it results in an error (as expected):

com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve subtype of [simple type, class Payload]: missing type id property 'type'

Because the type field is part of the CloudEvent header and/or envelope that Jackson does not know about at this point.

What's your take on this?

gustavomonarin commented 2 weeks ago

@mbechto in previous versions, while using the structure mode with access to the object mapper, this could be achieved easily with a mixin by adding an external behavior to the cloudevents class.

I believe the same could be used somehow, but to be honest, i am having a hard time navigating PojoCloudEventData its Mapper and escipally CloudEvent complete excluding any generic type. Maybe could someone share the reasoning, i am sure there was.

For Mix in approach (pay attention on the JsonTypeInfo.As.EXTERNAL_PROPERTY):

the mixin interface:

public interface CloudEventMixin<T> {

    @JsonTypeId
    String getType();

    @JsonTypeInfo(
            use = JsonTypeInfo.Id.NAME,
            include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
            property = "type",
            defaultImpl = Void.class)
    @JsonSubTypes({
            // 
            @JsonSubTypes.Type(value = Foo.class, name = "fulltypenameespace.foo")})
   T getData();

}

So was just to register this interface to the CloudEvents interface, in this case we could do the same with the pojo, but i believe the sdk could help more here :(

    Json.MAPPER
        .addMixIn(io.cloudevents.v1.CloudEventImpl.class, CloudEventMixIn.class)

I believe this could work if adjust your annotations to an interface targeting the PojoCloudEventData instead of my CloudEvent focused.