jackson多态序列化与反序列化

本文探讨了使用Jackson处理多态类型时的序列化与反序列化技巧,详细解析了@JsonTypeInfo和@JsonSubTypes注解的运用,确保子类信息在序列化过程中得以保留,同时保持序列化结果的简洁。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前几天遇到个问题,场景大概是 list 中既有父类又有子类,在反序列化时丢掉了子类的信息。解决这个问题也没花多少时间,不过还是打算记录下。参考链接如下:

Jackson JSON - Using @JsonTypeInfo annotation to handle polymorphic types

我使用 Jackson 实现序列化和反序列化。它在处理多态序列化时的思路是,构造一个起到标识作用的成员变量,并将该成员变量序列化到最终的字符串中,这样在反序列化的时候,jackson一看到这个标识符就知道该朝着哪个类进行反序列化了。

场景

父类:

public abstract class Shape {}

两个子类:

@Data
@ToString
public class Circle extends Shape {
  int radius;

  public static Circle of(int radius){
    Circle circle = new Circle();
    circle.setRadius(radius);
    return circle;
  }

}
@Data
@ToString
public class Rectangle extends Shape {
  private int w;
  private int h;

  public static Rectangle of(int w, int h) {
    Rectangle rectangle = new Rectangle();
    rectangle.setH(h);
    rectangle.setW(w);
    return rectangle;
  }
}

构建 list:

@Data
@ToString
public class View {
  private List<Shape> shapes;
}

序列化与反序列化的代码:

public class MainExample {

  public static void main(String[] args) throws Exception {
    View view = new View();
    view.setShapes(
        new ArrayList<Shape>() {
          {
            add(Rectangle.of(3, 6));
            add(Circle.of(5));
          }
        });

    System.out.println("---- serializing ---");
    ObjectMapper om = new ObjectMapper();
    String s = om.writeValueAsString(view);
    System.out.println(s);

    System.out.println("--- deserializing ---");
    View view1 = om.readValue(s,View.class);
    System.out.println(view1);

  }

它序列化以及反序列化后的结果:

---- serializing ---
{"shapes":[{"className":"rectangle1","w":3,"h":6},{"className":"circle1","radius":5}]}
--- deserializing ---
View(shapes=[Rectangle(w=3, h=6), Circle(radius=5)])

很明显,无论是 Rectangle 还是 Circle,在序列化后除了自己本省的属性对应的键值对外,各自都多了一个键值对,这个多出来的就是写进的子类信息。靠多出来的这个键值对,就可以保障反序列化的结果是正确的。

正如开头提及的,Jackson是将子列的信息写到了序列化后的字符串中,那具体是怎么写的呢?

它是这么写的:

@JsonTypeInfo(use = Id.NAME, include = As.PROPERTY, property = "className" ,visible = false)

public abstract class Shape {}

@JsonTypeInfo:Json类型信息,@JsonSubTypes : Json子类型。纠结这两个准确的意思很没有意思,也不是很必要,必要的是这两个货后面跟着的括弧中的那帮玩意到底表示什么。

@JsonTypeInfo(use = Id.NAME, include = As.PROPERTY, property = "className" ,visible = false)
@JsonSubTypes({
  @JsonSubTypes.Type(value = Rectangle.class, name = "rectangle1"),
  @JsonSubTypes.Type(value = Circle.class, name = "circle1")
})

这两个注解干了一件这样的事情:

include = As.PROPERTY 表示在 Shape 中添加一个成员变量(这个成员变量其实充当了一个类型标识符的作用),父类里面有这个变量,子类当然也就继承到了;property = "className" 表示这个成员变量的名字叫“className”,当然你可以写成别的阿猫阿狗;visible = false 表示“className”这个成员变量在反序列化的时候要被“剔除”掉,也就是说该属性不参与反序列化;use = Id.NAME 表示成员变量className对应的值由用户自己定义,只要不重复就行。那这个值到底是什么呢,@JsonSubTypes.Type(value = Rectangle.class, name = "rectangle1") 表示 Rectangle 的实例中成员变量className 的值是rectangle1Circle 也是同样的表示方式。

所以就出现可这样的序列化结果:

---- serializing ---
{"shapes":[{"className":"rectangle1","w":3,"h":6},{"className":"circle1","radius":5}]}

子类的类型信息被“塞到”序列化后的字符串中。

放反序列化的时候,jackson 一看到 rectangle1 就知道这个json字符串要向 Rectangle.class 反序列化,看到 circle1 就向 Circle.class 反序列化。说白了,就是在序列化后的json字符串中添加了一个具有标志作用的键值对,Jackson通过这个键值对确定json应该朝着那个类反序列化。

稍稍变一丢丢

先看啊,序列化的字符串中多了个键值对(虽说反序列化时又给去掉了),假如我要将序列化后的字符串保存起来,mybatis中存json的场景在开发中可是经常会出现的,对象序列化后平白无故多了一些信息,而且还給保存下来了,总归觉得有点别扭。

我着实遇到过这样的场景:创建的 List<object> 需要持久化到 MySQL,并且还要返回给前端。持久化要求序列化和反序列化必须成功。返回给前端要求序列化后不能出现多余的键值对。

如何既能不多信息,又能保证多态反序列化结果正确呢?很简单,在类中找到一个字段,要求不同子类中这个字段的值是不一样的,甚至子类和父类中这个字段的值也是不一样的,拿这个字段作为反序列化的区分标志就行了。

上面的场景改一下:

@Data
@ToString
public class Circle extends Shape {
  int radius;
  private String type = "CIR";

  public static Circle of(int radius){
    Circle circle = new Circle();
    circle.setRadius(radius);
    return circle;
  }

}
@Data
@ToString
public class Rectangle extends Shape {
  private int w;
  private int h;

  private String type = "REC";

  public static Rectangle of(int w, int h) {
    Rectangle rectangle = new Rectangle();
    rectangle.setH(h);
    rectangle.setW(w);
    return rectangle;
  }
}
@JsonTypeInfo(use = Id.NAME, include = As.EXISTING_PROPERTY, property = "type" ,visible = true)
@JsonSubTypes({
  @JsonSubTypes.Type(value = Rectangle.class, name = "REC"),
  @JsonSubTypes.Type(value = Circle.class, name = "CIR")
})
public abstract class Shape {}

序列化与反序列化结果:

---- serializing ---
{"shapes":[{"w":3,"h":6,"type":"REC"},{"radius":5,"type":"CIR"}]}
--- deserializing ---
View(shapes=[Rectangle(w=3, h=6, type=REC), Circle(radius=5, type=CIR)])

至于过程这个自己分析啦,很简单的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值