@JsonTypeInfo 处理多态、序列化对象类型

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

使用 @JsonTypeInfo

该注解会将有关多态实例的实际类型的信息序列化到 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}]}

@JsonTypeInfo 用于字段上

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 = WRAPPER_OBJECT、WRAPPER_ARRAY

使用 include = JsonTypeInfo.As.PROPERTY 可以将多态信息包装到 JSON 的属性中,但有时无法这样做(比如属性名冲突时),这时可以将其序列化为包装器。

JsonTypeInfo.As.WRAPPER_OBJECT

将 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}]}

JsonTypeInfo.As.WRAPPER_ARRAY

将 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}]}

使用 ObjectMapper.activateDefaultTyping()

这是一种全局的方式:

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<Shape> 也被写入了实际类型 ArrayList

原文链接

  1. Jackson JSON - Using @JsonTypeInfo annotation to handle polymorphic types
  2. Jackson JSON - Using @JsonTypeInfo include options WRAPPER_OBJECT and WRAPPER_ARRAY to wrap type information
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值