关于Gson的几个坑

"为何有些序列化的结果出乎你想象?"

Posted by Ariescat on March 12, 2020

废话不多说,直接上正文

  1. 坑1:我们初始化Map之类的集合的时候会用如下优雅的方式:

    Map<String, String> map = new HashMap<String, String>() {
        {
    		put("cat", "cat");
        }
    };
    Gson gson = new Gson();
    System.out.println(gson.toJson(map));
    

    但是会发现序列化后为null;这是因为上述方式产生的map是匿名内部类的实例,也就是说new出来的map没有类名。原因:com.google.gson.internal.Excluder#create,看源码得知Gson不序列化匿名和局部类。

    为何要这样设计呢?在github/gson的issues中找到了相关的描述(gson/issues/298),这里好像是说内部类的适配器会生成对外部类/实例的隐式引用,会导致循环引用(英语不好,应该是这样描述…)

  2. 坑2:

    Map<String, String> map = new HashMap<String, String>() {
        {
    		put("cat", "cat");
        }
    };
    Set<Map.Entry<String, String>> set2 = map.entrySet();
    Gson gson = new Gson();
    System.out.println(gson.toJson(set2))
    

    结果为[{}],如上也是无法转换json的,原因看源码:com.google.gson.internal.bind.ReflectiveTypeAdapterFactory#getBoundFields

    if (raw.isInterface()) { // 这里raw是“集合元素”的类型,为java.util.Map.Entry,是一个接口!
    	return result;
    }
    

    这里的map就算是普通的map(不使用构造代码块),也有一样的问题。因此,集合的泛型不要用接口、匿名内部类(匿名内部类会有坑1的问题,序列化结果为[null])。

  3. 阿里的fastjson不会有上述问题

    稍微看一下fastjson的底层实现:

    com.alibaba.fastjson.serializer.SerializeConfig#createJavaBeanSerializer(java.lang.Class<?>)

    这里调用了几个方法:

    1. buildBeanInfo,这里有个参数fieldBased默认为false,因此会调用computeGetters方法
    2. createASMSerializer(beanInfo)

    大概可以看出来在序列化的时候,先利用反射找到对象类的所有get方法,接下来去get,然后小写化,作为json的每个key值,而get方法的返回值作为value。接下来再反射field,添加到json中。

附:上文的测试代码 > study-metis/json-test


喜迎
春节