Jackson里使用@JsonTypeInfo注解处理多态类型的序列化和反序列化

在将多态类型进行JSON序列化后,Jackson无法在反序列化期间找出正确的类型。

public class Shape {
}

@Data
@NoArgsConstructor
@RequiredArgsConstructor(staticName = "of")
public class Rectangle extends Shape {
    // 长
    @NonNull
    private int length;
    // 宽
    @NonNull
    private int width;
}

@Data
@NoArgsConstructor
@RequiredArgsConstructor(staticName = "of")
public class Square extends Shape {
    // 边长
    @NonNull
    private int sideLength;
}

@Data
@NoArgsConstructor
@RequiredArgsConstructor(staticName = "of")
public class Circle extends Shape {
    // 半径
    @NonNull
    private int radius;
}


// 图形容器
@Data
@NoArgsConstructor
@RequiredArgsConstructor(staticName = "of")
public class Containers {
    @NonNull
    private List<Shape> shapes;
}

下面开始序列化和反序列化一个图形Containers实例

public class App {

    public static void main(String[] args) throws Exception {
        ObjectMapper objectMapper = new ObjectMapper();
        List<Shape> shapes = Lists.newArrayList(
                Rectangle.of(10, 20),
                Circle.of(100),
                Square.of(50)
        );

        // serializing
        String jsonString = objectMapper.writeValueAsString(Containers.of(shapes));
        System.out.println("jsonString=" + jsonString);
        System.out.println();


        // deserializing
        Containers containers = objectMapper.readValue(jsonString, Containers.class);
        System.out.println("containers=" + containers);
    }

}

出现如下错误

jsonString={"shapes":[{"length":10,"width":20},{"radius":100},{"sideLength":50}]}

Exception in thread "main" com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "length" (class com.jaemon.entity.Shape), not marked as ignorable (0 known properties: ])

使用@JsonTypeInfo

此注解用于序列化有关多态实例的实际类的信息,以便Jackson可以知道要反序列化的子类型。

可以使用以下注释来解决上面出现的异常:

@JsonTypeInfo(
    use = JsonTypeInfo.Id.CLASS, 
    include = JsonTypeInfo.As.PROPERTY, 
    property = "className"
)
public class Shape {
}

上面的配置指定使用的类名称(use = JsonTypeInfo.Id.CLASS),并将其作为JSON属性保留(include = JsonTypeInfo.As.PROPERTY)。 属性名称指定为’className’。

再次运行

jsonString={"shapes":[{"@class":"com.jaemon.entity.Rectangle","length":10,"width":20},{"@class":"com.jaemon.entity.Circle","radius":100},{"@class":"com.jaemon.entity.Square","sideLength":50}]}

containers=Containers(shapes=[Rectangle(length=10, width=20), Circle(radius=100), Square(sideLength=50)])

在上述配置中,元素“ include”和“ property”都是可选的,不写将使用默认值。 默认的“ include”也是JsonTypeInfo.As.PROPERTY,默认的“ property”是@class。

其他写法及程序运行输出如下

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
/**
	jsonString={"shapes":[{"@class":"com.jaemon.entity.Rectangle","length":10,"width":20},{"@class":"com.jaemon.entity.Circle","radius":100},{"@class":"com.jaemon.entity.Square","sideLength":50}]}

	containers=Containers(shapes=[Rectangle(length=10, width=20), Circle(radius=100), Square(sideLength=50)])
*/

@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS)
/**
	jsonString={"shapes":[{"@c":".Rectangle","length":10,"width":20},{"@c":".Circle","radius":100},{"@c":".Square","sideLength":50}]}

	containers=Containers(shapes=[Rectangle(length=10, width=20), Circle(radius=100), Square(sideLength=50)])
*/

@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS, property = "className")
/**
	jsonString={"shapes":[{"className":".Rectangle","length":10,"width":20},{"className":".Circle","radius":100},{"className":".Square","sideLength":50}]}

	containers=Containers(shapes=[Rectangle(length=10, width=20), Circle(radius=100), Square(sideLength=50)])
*/

use = JsonTypeInfo.Id.MINIMAL_CLASS选项将序列化最小的相对程序包路径。

在属性上使用@JsonTypeInfo

@JsonTypeInfo注释可以在类(上面的示例)和属性上使用。

例如可以在’shapes’属性上使用注解:

@Data
@NoArgsConstructor
@RequiredArgsConstructor(staticName = "of")
public class Containers {
    @NonNull
    @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
    private List<Shape> shapes;
}

如果该注解同时作用在类和属性上,则以使用在属性上的注解为准,因为它被认为更具体。

需要特别说明的是, 当@JsonTypeInfo在属性(字段,方法)上使用时,此注解适用于值。 当在集合类型(List,Map,Array)上使用时,它将应用于元素,而不是集合本身。 对于非集合类型,没有区别。

也就是说,在上面的代码中,当我们将其用于“shapes”的list时,它是应用于list里的每个元素(shape),而不是list类型本身。

全局Default Typing机制

除了使用@JsonTypeInfo注解来实现多态数据绑定,还可以使用全局Default Typing机制。

// default to using DefaultTyping.OBJECT_AND_NON_CONCRETE
objectMapper.enableDefaultTyping(); 
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

DefaultTyping有四个选项

  • JAVA_LANG_OBJECT: 当对象属性类型为Object时生效
  • OBJECT_AND_NON_CONCRETE: 当对象属性类型为Object或者非具体类型(抽象类和接口)时生效
  • NON_CONCRETE_AND+_ARRAYS: 同上, 另外所有的数组元素的类型都是非具体类型或者对象类型
  • NON_FINAL: 对所有非final类型或者非final类型元素的数组

开启DefaultTyping,并且让所有的非final类型对象持久化时都存储类型信息显然能准确的反序列多态类型的数据。

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.activateDefaultTyping(
    LaissezFaireSubTypeValidator.instance, 
    ObjectMapper.DefaultTyping.NON_FINAL, 
    JsonTypeInfo.As.PROPERTY
);
<dependencies>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.11.2</version>
    </dependency>
</dependencies>
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值