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 多态反序列化属性/类型丢失问题的主要内容,如果未能解决你的问题,请参考以下文章

Newtonsoft.Json 处理多态类型的反序列化

在 Java 中的 Jackson JSON 反序列化期间忽略丢失的属性

基于唯一属性的存在用 Jackson 反序列化多态类型

如何在Kotlin中用GSON实现多态列表反序列化?

具有 IEnumerable<ISomeInterface> 类型属性的 NewtonSoft.Json 序列化和反序列化类

System.Text.Json 中是不是可以进行多态反序列化?