java - Polymorphic deserialization in Jackson without annotations
问题描述
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).
解决方案
即使你不能修改你的类,你仍然可以依赖注解。Jackson 支持一种称为mix-ins的功能:您可以将其视为一种在运行时添加更多注释的面向方面的方式,以增加静态定义的注释。
首先定义一个接口如下:
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();
}
然后配置ObjectMapper
为使用定义的接口作为实际类/接口的混合:
ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(CloudEvent.class, CloudEventMixIn.class);
从addMixIn(Class<?> target, Class<?> mixinSource)
方法文档:
用于添加混合注释以用于扩充指定类或接口的方法。来自的所有注释
mixinSource
都被用来覆盖target
(或其超类型)具有的注释。
推荐阅读
- html - 属性绑定内部的属性绑定
- scala - 以任意数量的另一个函数作为参数的函数
- jpa - apache aries 如何恢复失败的事务?
- reactjs - 使用 react-dnd 和 useDrag 和 useDrop 进行测试
- python - 您如何编写多种语言的应用程序?
- javascript - 简单的登录验证器 - Krishna Prashatt & phuzi 回答
- javascript - 从 node_modules 导入/导出如何工作?
- svelte - 在 svelte 3 中向 50 多个嵌套组件发出信号以执行子组件功能的最佳方式是什么
- jquery - 如何正确编写 jquery 脚本
- android - 如何抑制或摆脱失败的断言:布尔表达式在颤振中不能为空警告?