前几天遇到个问题,场景大概是 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 的值是rectangle1 ,Circle 也是同样的表示方式。
所以就出现可这样的序列化结果:
---- 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)])
至于过程这个自己分析啦,很简单的。
本文探讨了使用Jackson处理多态类型时的序列化与反序列化技巧,详细解析了@JsonTypeInfo和@JsonSubTypes注解的运用,确保子类信息在序列化过程中得以保留,同时保持序列化结果的简洁。
646

被折叠的 条评论
为什么被折叠?



