在 JSON 中存在多态类型的情况下,Jackson 无法在反序列化期间找出正确的类型。让我们通过一个例子来理解它。
public abstract class Shape {
}
public class Rectangle extends Shape {
private int w;
private int h;
...
}
public class Circle extends Shape {
int radius;
...
}
public class View {
private List<Shape> shapes;
...
}
public class ExampleMain {
public static void main(String[] args) throws IOException {
View v = new View();
v.setShapes(new ArrayList<>(List.of(Rectangle.of(3, 6), Circle.of(5))));
System.out.println("-- serializing --");
ObjectMapper om = new ObjectMapper();
String s = om.writeValueAsString(v);
System.out.println(s);
System.out.println("-- deserializing --");
View view = om.readValue(s, View.class);
System.out.println(view);
}
}
-- serializing --
{"shapes":[{"w":3,"h":6},{"radius":5}]}
-- deserializing --
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `org.example.c18.Shape` (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: (String)"{"shapes":[{"w":3,"h":6},{"radius":5}]}"; line: 1, column: 12] (through reference chain: org.example.c18.View["shapes"]->java.util.ArrayList[0])
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1764)
at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:400)
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1209)
at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:274)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer._deserializeFromArray(CollectionDeserializer.java:347)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:244)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:28)
at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:129)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:324)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:187)
at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:322)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4593)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3548)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3516)
at org.example.c18.ExampleMain.main(ExampleMain.java:26)
该注解会将有关多态实例的实际类型的信息序列化到 JSON 中,以便 Jackson 可以知道要反序列化的子类型。让我们通过使用以下注释来修复异常:
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "className")
public abstract class Shape {
}
以上配置将会产生以下效果:实际类型信息会以对象属性(include = JsonTypeInfo.As.PROPERTY)的方式序列化到 JSON 中,且属性名(property = “className”)为 className,属性的值为类的全限定名(use = JsonTypeInfo.Id.CLASS)。
-- serializing --
{"shapes":[{"className":"org.example.c18.Rectangle","w":3,"h":6},{"className":"org.example.c18.Circle","radius":5}]}
-- deserializing --
View{shapes=[Rectangle{w=3, h=6}, Circle{radius=5}]}
若都使用默认值,结果:
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
public abstract class Shape {
}
-- serializing --
{"shapes":[{"@class":"org.example.c18.Rectangle","w":3,"h":6},{"@class":"org.example.c18.Circle","radius":5}]}
-- deserializing --
View{shapes=[Rectangle{w=3, h=6}, Circle{radius=5}]}
public class View {
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "className")
private List<Shape> shapes;
...
}
若类和字段上都有注解,则以字段上的为准。
当用于字段级别时,注解都值起作用。当应用于容器类型(比如 java.util.Collection, java.util.Map, arrays)时,它应用于元素,而不是容器本身。
使用 include = JsonTypeInfo.As.PROPERTY
可以将多态信息包装到 JSON 的属性中,但有时无法这样做(比如属性名冲突时),这时可以将其序列化为包装器。
将 JSON 对象包装为另一个对象,键为实际类型,值为原 JSON 对象:
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.WRAPPER_OBJECT, property = "className")
public abstract class Shape {
}
-- serializing --
{"shapes":[{"org.example.c18.Rectangle":{"w":3,"h":6}},{"org.example.c18.Circle":{"radius":5}}]}
-- deserializing --
View{shapes=[Rectangle{w=3, h=6}, Circle{radius=5}]}
将 JSON 对象包装为只包含 2 个元素的数组,第一个元素为实际类型,第二个元素为原 JSON 对象:
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.WRAPPER_ARRAY, property = "className")
public abstract class Shape {
}
-- serializing --
{"shapes":[["org.example.c18.Rectangle",{"w":3,"h":6}],["org.example.c18.Circle",{"radius":5}]]}
-- deserializing --
View{shapes=[Rectangle{w=3, h=6}, Circle{radius=5}]}
这是一种全局的方式:
public class ExampleMain2 {
public static void main(String[] args) throws IOException {
View v = new View();
v.setShapes(new ArrayList<>(List.of(Rectangle.of(3, 6), Circle.of(5))));
System.out.println("-- serializing --");
ObjectMapper om = new ObjectMapper();
om.activateDefaultTyping(om.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE, JsonTypeInfo.As.PROPERTY);
String s = om.writeValueAsString(v);
System.out.println(s);
System.out.println("-- deserializing --");
View view = om.readValue(s, View.class);
System.out.println(view);
}
}
-- serializing --
{"shapes":["java.util.ArrayList",[{"@class":"org.example.c18.Rectangle","w":3,"h":6},{"@class":"org.example.c18.Circle","radius":5}]]}
-- deserializing --
View{shapes=[Rectangle{w=3, h=6}, Circle{radius=5}]}
List
也被写入了实际类型 ArrayList
。