FastJson序列化结果与Gson,jackson的区别

0. 概述

在对Json序列化的测试中,我发现,FastJson对Map的序列化结果似乎与Gson以及Jackson都有不同,它使用了key的原始类型作为序列化之后的结果,而Gson和Jackson则是将key转换为了String类型。

在Json协议中,并没有对Map类型的数据结构有明确的规定,但是对Object类型有明确的要求,它的名称必须是String类型的,基于这个特点,Gson和Jackson都会将key转换为String类型,但是FastJson的默认实现并没有这样做。如果遇到对Json中Map格式要求严格与Object相同的接收方,就有可能会导致对方的Json解析失败。

FastJson当然是支持将Key转换为string类型的,但这并不是一个默认实现,是需要自己手动开启的选项,能够支持当然是好的,但这很难不让人担忧是不是还有其它未知的"默认实现"尚未被发现。

1. 失败的Json解析

近期在做一个涉及到Json序列化的改动的时候发现每当收到数据前端就会报错,在仔细的对比改动前后的Json数据之后,发现了端倪,我们使用了相同的数据结构,Map<Integer, List<Integer>>,但是最终序列化之后的结果却截然不同:

FastJson的结果:

json 复制代码
{1:[],2:[]}

Gson的结果:

json 复制代码
{"1":[],"2":[]}

这显然是很令人惊讶的,同一个类型的数据使用同一个协议竟然在不同的序列化工具中产生了不同的结果,解决问题当然很简单,只要使用Gson做序列化就可以了,但是疑惑却长久存在,是什么导致了这样的结果?

2. 更深入的测试

这样的区别会只发生在FastJson和Gson上吗?Jackson的测试结果是会与二者之一相同,还是会带来又一种不同的结果呢?带着疑惑,我开始进行更细致一些的测试

在接下来的测试中,我所使用的各个工具类的版本如下:

xml 复制代码
<dependency>
    <groupId>com.alibaba.fastjson2</groupId>
    <artifactId>fastjson2</artifactId>
    <version>2.0.46</version>
</dependency>

<dependency>
  <groupId>com.google.code.gson</groupId>
  <artifactId>gson</artifactId>
  <version>2.8.7</version>
</dependency>

2.1 测试三者对仅包含基本类型的对象的序列化

对象结构如下:

java 复制代码
public class Param {
    private Long updateTime = System.currentTimeMillis();
    private Boolean bool = false;
    private String str = "test";
    }

测试代码如下

csharp 复制代码
System.out.println(gson.toJson(new Param()));
System.out.println(objectMapper.writeValueAsString(new Param()));
System.out.println(JSON.toJSONString(new Param()));

测试结果如下:

vbnet 复制代码
Gson:  {"updateTime":1708588458365,"bool":false,"str":"test"}
Jackson:  {"updateTime":1708588458365,"bool":false,"str":"test"}
FastJson:  {"bool":false,"str":"test","updateTime":1708588458397}

看起来第一个不同之处已经出现了,FastJson的结果是按照字母序排列的,当然这是一个无关紧要的事情。

2.2 测试集合类型的序列化结果

测试代码如下:

java 复制代码
List<Integer> test=new ArrayList<>();
test.add(1);
test.add(2);
test.add(3);

System.out.println("Gson:  " + gson.toJson(test));
System.out.println("Jackson:  " + objectMapper.writeValueAsString(test));
System.out.println("FastJson:  " + JSON.toJSONString(test));

不同的集合类型数据内容完全一致,仅类型不同

2.2.1 测试List类型

测试结果如下:

ini 复制代码
Gson:  [1,2,3]
Jackson:  [1,2,3]
FastJson:  [1,2,3]

测试结论:三者相同

2.2.2 测试Set类型

测试结果如下:

ini 复制代码
Gson:  [1,2,3]
Jackson:  [1,2,3]
FastJson:  [1,2,3]

测试结论:三者相同

2.3 测试Map类型

Map类型比较特殊,它的结构与object十分相似,是key/value键值对的形式。在前面的测试中value的结果已经能够确定是相同的,因此Map的测试会对不同的key的类型进行测试。

2.3.1 测试string作为key的场景

测试代码如下

java 复制代码
Map<String, List<Integer>> test = new HashMap<>();
test.put("a", Collections.emptyList());
test.put("b", Collections.emptyList());
test.put("c", Collections.emptyList());

System.out.println("Gson:  " + gson.toJson(test));
System.out.println("Jackson:  " + objectMapper.writeValueAsString(test));
System.out.println("FastJson:  " + JSON.toJSONString(test));

测试结果如下

ruby 复制代码
Gson:  {"a":[],"b":[],"c":[]}
Jackson:  {"a":[],"b":[],"c":[]}
FastJson:  {"a":[],"b":[],"c":[]}

测试结论:三者相同

2.3.2 测试bool作为key的场景

测试代码如下:

java 复制代码
Map<Boolean, List<Integer>> test = new HashMap<>();
test.put(true, Collections.emptyList());
test.put(false, Collections.emptyList());

System.out.println("Gson:  " + gson.toJson(test));
System.out.println("Jackson:  " + objectMapper.writeValueAsString(test));
System.out.println("FastJson:  " + JSON.toJSONString(test));

测试结果如下:

vbnet 复制代码
Gson:  {"false":[],"true":[]}
Jackson:  {"false":[],"true":[]}
FastJson:  {false:[],true:[]}

测试结论:Gson与Jackson相同,将key转换为了string类型,FastJson使用了key的原始类型

2.3.3 测试数字类型作为key的场景

测试代码如下:

java 复制代码
Map<Integer, List<Integer>> test = new HashMap<>();
test.put(1, Collections.emptyList());
test.put(2, Collections.emptyList());
test.put(3, Collections.emptyList());

System.out.println("Gson:  " + gson.toJson(test));
System.out.println("Jackson:  " + objectMapper.writeValueAsString(test));
System.out.println("FastJson:  " + JSON.toJSONString(test));

测试结果如下:

ruby 复制代码
Gson:  {"1":[],"2":[],"3":[]}
Jackson:  {"1":[],"2":[],"3":[]}
FastJson:  {1:[],2:[],3:[]}

测试结论:Gson与Jackson相同,将key转换为了string类型,FastJson使用了key的原始类型

2.3.4 测试Object类型作为key的场景

测试代码如下:

java 复制代码
public class Param {
    private Long updateTime = System.currentTimeMillis();

    private Boolean bool = false;

    private String str = "test";
    
}
java 复制代码
Map<Param, List<Integer>> test = new HashMap<>();
test.put(new Param(), Collections.emptyList());
test.put(new Param(), Collections.emptyList());

System.out.println("Gson:  " + gson.toJson(test));
System.out.println("Jackson:  " + objectMapper.writeValueAsString(test));
System.out.println("FastJson:  " + JSON.toJSONString(test));

测试结果如下:

css 复制代码
Gson:  {"Param(updateTime\u003d1708594086203, bool\u003dfalse, str\u003dtest)":[]}
Jackson:  {"Param(updateTime=1708594086203, bool=false, str=test)":[]}
FastJson:  {{"bool":false,"str":"test","updateTime":1708594086203}:[]}

测试结论:Gson和Jackson结构类似,是,但是Gson出现了乱码,FastJson是对象的Json格式,但是仍然没有转换成String类型

2.4 结论

FastJson在涉及到Map类型时,对key应该为什么类型的默认实现与另外两种序列化方式产生了不同,它倾向于使用数据原本的类型来作为序列化之后的key值。

那么,这种行为是符合Json的规范的吗?

3. Json的协议到底是如何规定的?

在对Json格式进行规范的协议RFC4627中,提到了Json支持String、number、boolean、null四种基本类型以及 Object、array这两种结构化类型

JSON can represent four primitive types (strings, numbers, booleans, and null) and two structured types (objects and arrays).

显然,这其中没有提到Map类型应该是什么样子的,但是,如果没有提到Map类型,Map本身显然也是一个对象,我们按照Object类型来序列化是没有问题的。 那么,Object类型的又是怎么要求数据结构的呢?我们再来看协议:

An object structure is represented as a pair of curly brackets surrounding zero or more name/value pairs (or members). A name is a string. A single colon comes after each name, separating the name from the value. A single comma separates a value from a following name. The names within an object SHOULD be unique. object = begin-object [ member *( value-separator member ) ] end-object member = string name-separator value

我们可以看到,协议中规定对象是一个或多个被包含大括号中的 名称/值 组合,同时要求名称需要是一个字符串。

在Map这个场景下,如果我们将Map中保存的数据理解为需要序列化的对象,那么key应当遵循名称规范使用String类型,或者就需要将Map序列化为Entry的集合。

4 总结

经过上面的讨论我们能够知道,FastJson在Map类型的序列化结果上面的设计理念与Jackson和Gson是有区别的,尽管它也支持通过配置调整成一致的结果,但这仍然值得担忧,因为不知道是不是还会有其他隐蔽地方的设计理念会有所不同。 我们在测试中还发现了一个有趣的现象,在Map的key为Object类型的时候,Gson与Jackson的结构看上去并不是一个正常的Json结构,这是因为它们底层调用的是key的toString方法,如果重写这个方法,就能够定制key的结果,当然,仍然得是String类型的。

相关推荐
之诺1 分钟前
MySQL通信过程字符集转换
后端·mysql
喵手2 分钟前
反射机制:你真的了解它的“能力”吗?
java·后端·java ee
用户466537015053 分钟前
git代码压缩合并
后端·github
武大打工仔7 分钟前
从零开始手搓一个MVC框架
后端
开心猴爷12 分钟前
移动端网页调试实战 Cookie 丢失问题的排查与优化
后端
用户57240561413 分钟前
解析Json
后端
舒一笑14 分钟前
Mac 上安装并使用 frpc(FRP 内网穿透客户端)指南
后端·网络协议·程序员
每天学习一丢丢19 分钟前
Spring Boot + Vue 项目用宝塔面板部署指南
vue.js·spring boot·后端
邹小邹20 分钟前
Go 1.25 强势来袭:GC 速度飙升、并发测试神器上线,内存检测更精准!
后端·go
lichenyang45323 分钟前
管理项目服务器连接数据库
数据库·后端