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类型的。

相关推荐
kylinxjd2 分钟前
spring boot发送邮件
java·spring boot·后端·发送email邮件
2401_857439693 小时前
Spring Boot新闻推荐系统:用户体验优化
spring boot·后端·ux
进击的女IT4 小时前
SpringBoot上传图片实现本地存储以及实现直接上传阿里云OSS
java·spring boot·后端
一 乐5 小时前
学籍管理平台|在线学籍管理平台系统|基于Springboot+VUE的在线学籍管理平台系统设计与实现(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·学习
艾伦~耶格尔8 小时前
Spring Boot 三层架构开发模式入门
java·spring boot·后端·架构·三层架构
man20178 小时前
基于spring boot的篮球论坛系统
java·spring boot·后端
攸攸太上9 小时前
Spring Gateway学习
java·后端·学习·spring·微服务·gateway
罗曼蒂克在消亡9 小时前
graphql--快速了解graphql特点
后端·graphql
潘多编程9 小时前
Spring Boot与GraphQL:现代化API设计
spring boot·后端·graphql
大神薯条老师10 小时前
Python从入门到高手4.3节-掌握跳转控制语句
后端·爬虫·python·深度学习·机器学习·数据分析