前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JSON 多态反序列化属性/类型丢失问题

JSON 多态反序列化属性/类型丢失问题

作者头像
明明如月学长
发布2021-10-18 10:25:24
3.8K0
发布2021-10-18 10:25:24
举报
文章被收录于专栏:明明如月的技术专栏

一、背景

工作中有时候会遇到一个类定义了某个类型的父类作为成员变量,实际存放的为某个子类型, JSON 反序列化后,属性丢失的情况。

如果你赶时间,可以直接跳到第三部分看解决方案。

二、模拟

父类型

代码语言:javascript
复制
package json;

import lombok.Data;

@Data
public class Parent {
    private String name;
    private String sex ;
}

子类型1:

代码语言:javascript
复制
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:

代码语言:javascript
复制
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;
}

演示类:

代码语言:javascript
复制
package json;

import lombok.Data;

@Data
public class Some {
    private Parent parent;
}

验证代码:

代码语言:javascript
复制
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);
    }
}

执行结果:

代码语言:javascript
复制
序列化后:{"parent":{"c1Field":"C1子类特有属性","name":"张三","sex":"男"}}
反序列化后:Some(parent=Parent(name=张三, sex=男))

这样存在的问题: 1、有时候我们会依据 Parent 的具体子类型来执行不同的策略,由于无法确定类型,给我们的编码带来了困扰 2、反序列化时,由于无法感知序列化时 Parent 类的具体类型,反序列化丢失了 other 成员变量的值。

三、解决办法

3.1 将子类型写入 JSON 字符串

先说一个常规做法。

如果我们在序列化时将具体的子类型写入到 JSON 字符串中,反序列化时就可以使用该子类型对其进行反序列化。

以 fastjson 为例,可以在调用 JSON#toJSONString 时,指明写入类名。

代码语言:javascript
复制
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 子类对应的策略");
            }
    }
}

运行结果

代码语言:javascript
复制
序列化后:{"@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 类中。

代码语言:javascript
复制
package json;

import lombok.Data;

@Data
public class Some {
    private Child child;
    private Child2 child2;
}

这样反序列化不丢失属性,而且还可以根据子类型来执行不同策略。

代码语言:javascript
复制
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 子类对应的策略");
        }
    }
}

结果:

代码语言:javascript
复制
序列化后:{"child":{"c1Field":"C1子类特有属性","name":"张三","sex":"男"}}
反序列化后:Some(child=Child(super=Parent(name=张三, sex=男), c1Field=C1子类特有属性), child2=null)
执行 Child 子类对应的策略

3.3 加入标记

如果我们不想将每个子类型都写入到 Some 类中,我们还可以在 Some 类中新增一个 String type 字段来标识具体是哪个子类型。

然后提供一个工厂方法即可。

代码语言:javascript
复制
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;
        }
    }
}

覆写

代码语言:javascript
复制
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 多态反序列化属性或类型丢失问题,并提供了几种解决方案,希望对大家有帮助。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/10/16 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、背景
  • 二、模拟
  • 三、解决办法
    • 3.1 将子类型写入 JSON 字符串
      • 3.2 打平
        • 3.3 加入标记
        • 四、总结
        相关产品与服务
        文件存储
        文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档