我正在尝试反序列化相当奇怪的类型,类型解析可以用
JsonTypeInfo.Id.NAME
部分解决,但在某些情况下它不能完全解析类型,而是缩小选择范围(意味着两种类型共享 type
的共同值)场地)。但这些缩小的类型可以通过现有字段来解决(使用JsonTypeInfo.Id.DEDUCTION
)。因此,我尝试通过引入使用 JsonTypeInfo.Id.NAME
注释所有类型的基本类型,然后引入缩小类型的中间超类型来实现此逻辑,该类型使用 JsonTypeInfo.Id.DEDUCTION
来完成其余的解析。
这是我想要实现的目标的最小示例:
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JacksonTest {
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", include = JsonTypeInfo.As.EXISTING_PROPERTY)
@JsonSubTypes({
@JsonSubTypes.Type(name = "a", value = A.class),
@JsonSubTypes.Type(name = "b", value = B.class),
})
interface Base {
String getType();
}
static class A implements Base {
private String b1;
@Override
public String getType() {
return "a";
}
public String getB1() {
return b1;
}
public A setB1(String b1) {
this.b1 = b1;
return this;
}
}
@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION)
@JsonSubTypes({
@JsonSubTypes.Type(value = B1.class),
@JsonSubTypes.Type(value = B2.class),
})
interface B extends Base {}
static class B1 implements B {
private String b1;
public String getB1() {
return b1;
}
public B1 setB1(String b1) {
this.b1 = b1;
return this;
}
@Override
public String getType() {
return "b";
}
}
static class B2 implements B {
private String b2;
@Override
public String getType() {
return "b";
}
public String getB2() {
return b2;
}
public B2 setB2(String b2) {
this.b2 = b2;
return this;
}
}
public static void main(String[] args) throws JsonProcessingException {
var om = new ObjectMapper();
var b1 = new B1();
b1.setB1("test");
var str = om.writeValueAsString(b1);
System.out.println(str);
var deser = om.readValue(str, Base.class);
System.out.println(deser);
}
}
我得到的回应是:
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `JacksonTest$B` (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 21]
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1887)
at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:414)
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1375)
at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:274)
at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedForId(AsPropertyTypeDeserializer.java:170)
at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:136)
at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserializeWithType(AbstractDeserializer.java:263)
at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:74)
at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:342)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4899)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3846)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3814)
at JacksonTest.main(JacksonTest.java:87)
一个合理的尝试是尝试使用先推导,然后使用显式类型字段(至少在我的情况和这个简单的情况下是有意义的)。我们必须稍微重新排列类型:
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JacksonTest2 {
@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION)
@JsonSubTypes({
@JsonSubTypes.Type(value = B2.class),
@JsonSubTypes.Type(value = WithB1.class),
})
interface Base {
String getType();
}
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", include = JsonTypeInfo.As.EXISTING_PROPERTY)
@JsonSubTypes({
@JsonSubTypes.Type(name = "a", value = A.class),
@JsonSubTypes.Type(name = "b", value = B1.class),
})
interface WithB1 extends Base {
String getB1();
}
static class A implements WithB1 {
private String b1;
@Override
public String getType() {
return "a";
}
public String getB1() {
return b1;
}
public A setB1(String b1) {
this.b1 = b1;
return this;
}
}
static class B1 implements WithB1 {
private String b1;
public String getB1() {
return b1;
}
public B1 setB1(String b1) {
this.b1 = b1;
return this;
}
@Override
public String getType() {
return "b";
}
}
static class B2 implements Base {
private String b2;
@Override
public String getType() {
return "b";
}
public String getB2() {
return b2;
}
public B2 setB2(String b2) {
this.b2 = b2;
return this;
}
}
public static void main(String[] args) throws JsonProcessingException {
var om = new ObjectMapper();
var b1 = new B1();
b1.setB1("test");
var str = om.writeValueAsString(b1);
System.out.println(str);
var deser = om.readValue(str, Base.class);
System.out.println(deser);
}
}
这里也没有运气:
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Subtypes JacksonTest2$A and JacksonTest2$B1 have the same signature and cannot be uniquely deduced.
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory.findTypeDeserializer(BasicDeserializerFactory.java:1834)
at com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(DeserializationContext.java:675)
at com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(ObjectMapper.java:5030)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4900)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3846)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3814)
at JacksonTest2.main(JacksonTest2.java:91)
Caused by: java.lang.IllegalStateException: Subtypes JacksonTest2$A and JacksonTest2$B1 have the same signature and cannot be uniquely deduced.
at com.fasterxml.jackson.databind.jsontype.impl.AsDeductionTypeDeserializer.buildFingerprints(AsDeductionTypeDeserializer.java:90)
at com.fasterxml.jackson.databind.jsontype.impl.AsDeductionTypeDeserializer.<init>(AsDeductionTypeDeserializer.java:49)
at com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder.buildTypeDeserializer(StdTypeResolverBuilder.java:212)
at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory.findTypeDeserializer(BasicDeserializerFactory.java:1832)
... 6 more
杰克逊可以实现这一目标吗?
回答我自己。
tl;dr:避免这样做:如果这是您生成的数据,请引入一些额外的类型信息,或者尝试说服数据供应商这样做。
似乎没有简单且开箱即用的方法可以做到这一点。
我最终摆脱了人为的类层次结构,并将自定义
@JsonTypeResolver
附加到您的类中并自己实现了这个解析器。附加 @JsonTypeIdResolver
是不够的,因为推演机制位于其之上。
以下是基础接口的注释:
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", include = JsonTypeInfo.As.EXISTING_PROPERTY)
@JsonSubTypes({
@JsonSubTypes.Type(name = "a", value = A.class),
@JsonSubTypes.Type(name = "b", value = B1.class),
@JsonSubTypes.Type(name = "b", value = B2.class),
})
实现
TypeDeserializer
涉及扩展标准 Jackson TypeDeserializer
之一并对其进行猴子修补。
第一个意图是通过 id 对传递给
subtypes
的 TypeResolverBuilder.buildTypeDeserializer
进行分组,将它们传递给 TypeDeserializer
,并在类型 id 不唯一时让它委托给 AsDeductionTypeDeserializer
。另一个需要注意的是,传递给 subtypes
的 StdTypeResolverBuilder.buildTypeDeserializer
永远不会有具有重复 ID 的类型,标准 SubtypeResolver
只是删除重复项。您还可以对其进行修补以在您的自定义中使用 TypeResolverBuilder
。
所以,这是您需要的猴子补丁类:
TypeResolverBuilder
您将使用注释附加到您的类并返回自定义 TypeDeserializer
TypeDeserializer
本身,它将使用缩小的子类型列表将类型反序列化委托给AsDeductionTypeDeserializer
SubtypeResolver
允许从 collectAndResolveSubtypesByTypeId
完整的答案对于答案来说相当大,所以我使用gist
来分享它