JSON 多态反序列化属性/类型丢失问题
Posted 明明如月学长
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JSON 多态反序列化属性/类型丢失问题相关的知识,希望对你有一定的参考价值。
一、背景
工作中有时候会遇到一个类定义了某个类型的父类作为成员变量,实际存放的为某个子类型, JSON 反序列化后,属性丢失的情况。
如果你赶时间,可以直接跳到第三部分看解决方案。
二、模拟
父类型
package json;
import lombok.Data;
@Data
public class Parent {
private String name;
private String sex ;
}
子类型1:
package json;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@EqualsAndHashCode(callSuper = true)
@Data
@ToString( callSuper = true)
public class Child extends Parent {
private String c1Field;
}
子类型2:
package json;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@EqualsAndHashCode(callSuper = true)
@Data
@ToString( callSuper = true)
public class Child2 extends Parent{
private String c2Filed;
}
演示类:
package json;
import lombok.Data;
@Data
public class Some {
private Parent parent;
}
验证代码:
package json;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.core.JsonProcessingException;
public class ObjectDemo {
public static void main(String[] args) throws JsonProcessingException {
Some some = new Some();
Child child = new Child();
child.setName("张三");
child.setSex("男");
child.setC1Field("C1子类特有属性");
some.setParent(child);
String jsonStr = JSON.toJSONString(some);
System.out.println("序列化后:"+jsonStr);
Some result = JSON.parseObject(jsonStr,Some.class);
System.out.println("反序列化后:"+result);
}
}
执行结果:
序列化后:{"parent":{"c1Field":"C1子类特有属性","name":"张三","sex":"男"}}
反序列化后:Some(parent=Parent(name=张三, sex=男))
这样存在的问题:
1、有时候我们会依据 Parent 的具体子类型来执行不同的策略,由于无法确定类型,给我们的编码带来了困扰
2、反序列化时,由于无法感知序列化时 Parent 类的具体类型,反序列化丢失了 other 成员变量的值。
三、解决办法
3.1 将子类型写入 JSON 字符串
先说一个常规做法。
如果我们在序列化时将具体的子类型写入到 JSON 字符串中,反序列化时就可以使用该子类型对其进行反序列化。
以 fastjson 为例,可以在调用 JSON#toJSONString
时,指明写入类名。
package json;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.core.JsonProcessingException;
public class ObjectDemo {
public static void main(String[] args) throws JsonProcessingException {
Some some = new Some();
Child child = new Child();
child.setName("张三");
child.setSex("男");
child.setC1Field("C1子类特有属性");
some.setParent(child);
// 写入类名
String jsonStr = JSON.toJSONString(some, SerializerFeature.WriteClassName);
System.out.println("序列化后:"+jsonStr);
Some result = JSON.parseObject(jsonStr,Some.class);
System.out.println("反序列化后:"+result);
// 判断子类型
if(result.getParent() instanceof Child){
System.out.println("执行 Child 子类对应的策略");
}else
if(result.getParent() instanceof Child2){
System.out.println("执行 Child2 子类对应的策略");
}
}
}
运行结果
序列化后:{"@type":"json.Some","parent":{"@type":"json.Child","c1Field":"C1子类特有属性","name":"张三","sex":"男"}}
反序列化后:Some(parent=Child(super=Parent(name=张三, sex=男), c1Field=C1子类特有属性))
执行 Child 子类对应的策略
其他 JSON 序列化工具都有自己特定的方式,大家直接参考各自的官方文档即可。
这样做的缺点是和具体的序列化工具绑定,如果上下游用的不是同一套工具而且相互不兼容,就非常尴尬了!!
3.2 打平
为了不合具体的 JSON 序列化工具绑定,我们可以选择打平。
我个人更倾向于这种方式!
即如果 Parent 有多个了子类型,如果下游需要根据不同的子类型执行不同的策略。
我们可以将Parent 的子类型直接定义在 Some 类中。
package json;
import lombok.Data;
@Data
public class Some {
private Child child;
private Child2 child2;
}
这样反序列化不丢失属性,而且还可以根据子类型来执行不同策略。
package json;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.core.JsonProcessingException;
public class ObjectDemo {
public static void main(String[] args) throws JsonProcessingException {
Some some = new Some();
Child child = new Child();
child.setName("张三");
child.setSex("男");
child.setC1Field("C1子类特有属性");
some.setChild(child);
String jsonStr = JSON.toJSONString(some);
System.out.println("序列化后:"+jsonStr);
Some result = JSON.parseObject(jsonStr,Some.class);
System.out.println("反序列化后:"+result);
if(result.getChild()!= null){
System.out.println("执行 Child 子类对应的策略");
}
}
}
结果:
序列化后:{"child":{"c1Field":"C1子类特有属性","name":"张三","sex":"男"}}
反序列化后:Some(child=Child(super=Parent(name=张三, sex=男), c1Field=C1子类特有属性), child2=null)
执行 Child 子类对应的策略
3.3 加入标记
如果我们不想将每个子类型都写入到 Some 类中,我们还可以在 Some 类中新增一个 String type 字段来标识具体是哪个子类型。
然后提供一个工厂方法即可。
package json;
public class ParentTypeFactory {
public static Class<? extends Parent> get(String type) {
switch (type) {
case "child1":
return Child.class;
case "child2":
return Child2.class;
default:
return null;
}
}
}
覆写
package json;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.core.JsonProcessingException;
public class ObjectDemo {
public static void main(String[] args) throws JsonProcessingException {
Some some = new Some();
Child child = new Child();
child.setName("张三");
child.setSex("男");
child.setC1Field("C1子类特有属性");
some.setParent(child);
some.setType("child1");
// 写入类名
String jsonStr = JSON.toJSONString(some);
System.out.println("序列化后:" + jsonStr);
Some result = JSON.parseObject(jsonStr, Some.class);
System.out.println("反序列化后:" + result);
JSONObject jsonObject = JSON.parseObject(jsonStr);
Parent parent = jsonObject.getObject("parent", ParentTypeFactory.get(result.getType()));
result.setParent(parent);
System.out.println("反序列化后并覆写parent:" + result);
// 判断子类型
if (result.getParent() instanceof Child) {
System.out.println("执行 Child 子类对应的策略");
} else if (result.getParent() instanceof Child2) {
System.out.println("执行 Child2 子类对应的策略");
}
}
}
虽然这种方法需要新增一个类型的成员变量,但各种 JSON 框架都可以根据这个标识进行解析。
四、总结
本文主要讲 JSON 多态反序列化属性或类型丢失问题,并提供了几种解决方案,希望对大家有帮助。
以上是关于JSON 多态反序列化属性/类型丢失问题的主要内容,如果未能解决你的问题,请参考以下文章
在 Java 中的 Jackson JSON 反序列化期间忽略丢失的属性
具有 IEnumerable<ISomeInterface> 类型属性的 NewtonSoft.Json 序列化和反序列化类