四时宝库

程序员的知识宝库

JSON序列化引发的问题(json的序列化和反序列化)

今天看到一个问题,说的是两次打印一个QueryWrapper对象,结果不一致


几天前这个作者已经问过一次同样的问题了,感觉有点意思,就研究了下这个问题
有个作者有句话说得挺好:源码之下无秘密

结果怎样,肯定取决于源码怎样,但这次我决定绕开源码来讨论这个问题
这个问题的核心问题点可以归结为一行代码,这行代码两次执行结果不一致

System.out.println( JSONUtil.toJsonStr(w) );

但System.out.println本身我们都比较熟悉,只是输出而已,再精简一下,就成了

JSONUtil.toJsonStr(w)

所以现在问题变成了JSONUtil.toJsonStr两次输出的结果为什么不一致

总结到这里,其实问题已经有答案了,那就是:

有规定JSONUtil.toJsonStr执行两次的结果得是一致的吗?

答案当然是没有,很多人陷入了思维惯性的误区:认为JSON序列化就是原样的输出对象本身
其实这个理解太概念化太肤浅了,java对象的JSON序列化本质只是执行你引用的JSON工具包内的代码,结果如何,取决于这工具底层的代码是如何编写的

但这里我们不研究底层的源码,只做对比实验,直接上代码

import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.google.gson.Gson;
import lombok.Getter;

import java.util.function.Function;

public class Test {


    public static void main(String[] args) {

        Gson gson = new Gson();

        printA(JSON::toJSONString, "fastjson2");
        printA(gson::toJson, "gson");
        printA(JSONUtil::toJsonStr, "hutool JSONUtil");
        
        System.out.println();
        System.out.println("========================");
        System.out.println();

        printQueryWrapper(JSON::toJSONString, "fastjson2");
        printQueryWrapper(JSONUtil::toJsonStr, "hutool JSONUtil");
        printQueryWrapper(gson::toJson, "gson");
    }


    private static void printA(Function<Object, String> f, String desc) {
        A a = new A();
        print(a, f, desc);
    }

    private static void printQueryWrapper(Function<Object, String> f, String desc) {
        QueryWrapper<Object> w = new QueryWrapper<>();
        w.like("cai", "菜");
        print(w, f, desc);

    }

    private static void print(Object a, Function<Object, String> f, String desc) {
        System.out.println(desc + " ------------------->");
        System.out.println(f.apply(a));
        System.out.println(f.apply(a));
        System.out.println(f.apply(a));
        System.out.println();

    }

}

@Getter
class A {
    private String a;

    private String b = "b";

    private String c;

    public String getB() {
        return "getB";
    }

    public String getC() {
        a = a == null ? "a" : a;
        a += "c";
        return "c";
    }

    public String getD() {
        a = a == null ? "a" : a;
        a += "d";
        return "d";
    }


}

打印结果如下


其中fastjson2输出的结果较长,关键内容如下

先说这几个工具打印对象a的结果

观察输出发现

  1. fastjson每次输出的结果都不一样,根据结果很容易推测,它每次都执行了A中的get方法,不管该get方法对应的属性是否真的存在
  2. hutool每次输出的结果也不一样,但不同的是没输出属性d,推测是因为不存在属性d,它没执行getD方法
  3. gson每次输出的结果都是一样,推测它是直接反射读取的属性,而没有执行get方法

当然,这些推论都可以通过看源码证实,只是在这里先不讨论

只需要明白这样一件事:各JSON序列化工具所遵守的规范是不一样的

在这里先不能说谁对谁错(至于是否有java官方的JSON序列化规范我没考证过)

正常情况下不会有人写出像类A这样的挖坑代码,所以大家工作时也不用太担心JSON工具给你挖坑

再来讨论对象wrapper的打印结果,可以看出

fastjson每次输出结果都不一样,显然原理同上

hutool第一次和后两次输出结果不一样

原因是为啥呢,这里为了方便直观我们回到对象a的输出结果,

观察hutool的第一行输出,输出了c的值"c",显然一定是执行了getC方法,

getC方法执行之后a肯定是有值的,但是为什么没打印出a的值?

原因只能是getA方法先于getC方法执行,至于为什么是这个执行顺序,明显也取决于JSON库的代码如何实现。

再回到刚才的问题,可以推论出,后两次结果一致是因为wrapper对象内的属性的值已经稳定了。

但为啥fastjson输出的结果每次都不一致呢,答案只能是fastjson每次都调用了某个get方法,而这个get方法修改了paramNameValuePairs的值的内容;那为什么hutool不会如此呢?综合之前的推论,答案显然是因为这个get方法没有对应的属性

gson输出的结果直接报错,通过错误可以看到栈溢出了,有经验的同学一眼就能看出来是出现循环引用了

通过以上这些信息,可以总结出以下问题点:

  1. JSON序列化结果如何,取决于JSON库如何实现
  2. JSON库如何实现,有两个思路,直接反射获取属性还是调用get方法
  3. 如果调用get方法,又会涉及到这两个问题:
  • 某个get方法的执行会不会影响其他get方法执行的结果
  • 这些get方法的执行顺序如何
  1. 如果采用反射直接获取属性值,怎么解决循环引用

最后,我总结一下出现这个问题的根本原因:

没有理解清 序列化 这个词的本质含义:数据才需要序列化,如果一个对象不是数据,那么讨论它的序列化行为没有意义

queryWrapper不是纯数据对象(java bean),所以它的序列化行为根本就是未定义的,mybatis-plus作者都再三强调过不要序列化传输QueryWrapper对象

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接