[JSON] Fastjson 之版本对比:Fastjson vs Fastjson2
0 序
1 Fastjson2
1.1 简述
- FASTJSON相对其他JSON库的特点是快,从2011年fastjson发布1.1.x版本之后,其性能从未被其他Java实现的JSON库超越。
-
Fastjson2相对Fastjson1版本可以说是一次完全重构。
- 这里从代码的角度,分析两者的一些区别;
- 并总结一些新的,让小伙伴们使用Fastjson2或者进行功能扩展的时候,能更加顺畅。
- 除了在性能方面的提升之外,Fastjson 2还解决了一系列安全方面的问题和兼容性的问题,这很大的提高了使用的便捷性。
-
FASTJSON v2是FASTJSON项目的重要升级,目标是为下一个十年提供一个高性能的JSON库。
-
通过同一套API:
- 更安全: AutoType 必须显式打开才能使用,没有任何白名单,也不包括任何 Exception 类的白名单。这可以保证缺省配置下是安全的。
- 支持JSON/JSONB两种协议;
- JSONPath 是一等公民;
- 支持全量解析和部分解析(基于
JSONPath
); - 支持Java服务端、客户端Android、大数据场景;支持Kotlin;
- 支持JSON Schema ;
- 支持Graal Native-Image。
1.2 安全漏洞问题
1.2.1 罪魁祸首 AutoType
fastjson、jackson 都支持 AutoType 功能,这个功能在序列化的 JSON 字符串中带上类型信息,在反序列化时,不需要传入类型,实现自动类型识别。
1.2.2 Fastjson V1
fastjson 1.x 内部维护了一个AutoType
的白名单,java 发展近 30 年难免有些漏网之鱼,这也造成近几年 fastjson 安全漏洞频发。
1.2.3 Fastjson V2
- fastjson2 AutoType 必须显式打开才能使用,没有任何白名单,也不包括任何 Exception 类的白名单。这可以保证缺省配置下是安全的。
- 序列化时带上类型信息,需要使用
JSONWriter.Feature.WriteClassName
。比如:
Bean bean = ...;
String jsonString = JSON.toJSONString(bean, JSONWriter.Feature.WriteClassName);
- 很多时候,root对象是可以知道类型的,里面的对象字段是基类或者不确定类型,这个时候不输出root对象的类型信息,可以减少序列化结果的大小,也能提升反序列化的性能。
Bean bean = ...;
String jsonString = JSON.toJSONString(bean, JSONWriter.Feature.WriteClassName, JSONWriter.Feature.NotWriteRootClassName);
- 反序列化打开AutoType功能支持自动类型
Bean bean = (Bean) JSON.parseObject(jsonString, Object.class, JSONReader.Feature.SupportAutoType);
- fastjson2 AutoType 支持配置 safeMode,在 safeMode 打开后,显式传入 AutoType 参数也不起作用,具体配置如下:
-Dfastjson2.parser.safeMode=true
- fastjson2 AutoType 会经过内置黑名单过滤。
- 虽然该黑名单能拦截大部分常见风险,但是这个机制不能保证绝对安全。
- 故:打开 AutoType 【不应该】在暴露在公网的场景下使用
2 版本对比
2.1 引入依赖
- V1
https://github.com/alibaba/fastjson/releases
https://repo1.maven.org/maven2/com/alibaba/fastjson/
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
截止 2023-11-07 的最新版本为 1.2.83
- V2
https://github.com/alibaba/fastjson2/releases
https://repo1.maven.org/maven2/com/alibaba/fastjson2/fastjson2/
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.42</version>
</dependency>
截止 2023-11-07 的最新版本为 2.0.42
截止 2024-08-09 的最新版本为 2.0.52
注意:groupId 和v1版本的相比,有所改变。
官方说明:如果原来使用fastjson 1.2.x版本,可以使用兼容包,兼容包不能保证100%兼容,请仔细测试验证,发现问题请及时反馈。兼容包坐标如下:
- 兼容包
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.1</version>
</dependency>
2.2 源码对比
2.2.1 整体继承类的修改
在fastjson 2.0中,package和1.x不一样,是com.alibaba.fastjson2。如果你之前用的是fastjson1,大多数情况直接更换包名就即可。
V1
//1.Fastjson 1 JSONObject类定义
public class JSONObject extends JSON implements Map<String,Object> ... {
}
//2.Fastjson 1 JSONArray类定义
public class JSONArray extends JSON implements List<Object> ... {
}
V2
//1.Fastjson2 JSONObject类定义
public class JSONObject extends LinkedHashMap<String, Object> implements InvocationHandler {
}
//2.Fastjson2 JSONArray类定义
public class JSONArray extends ArrayList<Object> {
}
Fastjson2
的JSONObject
实现了链结构的Map
,是有序的Map容器
。
无论是JSONObject
或者JSONArray
都摆脱了JSON
的类,使JSON
由抽象类变成了接口。
package com.alibaba.fastjson2;
class JSON {
// 将字符串解析成JSONObject
static JSONObject parseObject(String str);
// 将字符串解析成JSONArray
static JSONArray parseArray(String str);
// 将字符串解析成Java对象
static T parseObject(byte[] utf8Bytes, Class<T> objectClass);
// 将Java对象输出成字符串
static String toJSONString(Object object);
// 将Java对象输出成UT8编码的byte[]
static byte[] toJSONBytes(Object object);
}
class JSONB {
// 将jsonb格式的byte[]解析成Java对象
static T parseObject(byte[] jsonbBytes, Class<T> objectClass);
// 将Java对象输出成jsonb格式的byte[]
static byte[] toBytes(Object object);
}
class JSONObject {
Object get(String key);
int getIntValue(String key);
Integer getInteger(String key);
long getLongValue(String key);
Long getLong(String key);
T getObject(String key, Class<T> objectClass);
// 将JSONObject对象转换为Java对象
T toJavaObject(Class<T> objectClass);
}
class JSONArray {
Object get(int index);
int getIntValue(int index);
Integer getInteger(int index);
long getLongValue(int index);
Long getLong(int index);
T getObject(int index, Class<T> objectClass);
}
class JSONPath {
// 构造JSONPath
static JSONPath of(String path);
// 根据path直接解析输入,会部分解析优化,不会全部解析
Object extract(JSONReader jsonReader);
// 根据path对对象求值
Object eval(Object rootObject);
}
class JSONReader {
// 构造基于String输入的JSONReader
static JSONReader of(String str);
// 构造基于ut8编码byte数组输入的JSONReader
static JSONReader of(byte[] utf8Bytes);
// 构造基于char[]输入的JSONReader
static JSONReader of(char[] chars);
// 构造基于json格式byte数组输入的JSONReader
static JSONReader ofJSONB(byte[] jsonbBytes)
}
2.2.2 常见类型的优化
-
时间转化类由原来使用SimpleDateFormat转化为 JDK8 提供的
java.time API
,吸收了 joda-time的部分精华,功能更强大,性能也更好。 -
同时,
DateTimeFormatter
是线程安全的。 -
测试类
@Builder
@Data
@ToString
public class Entity {
private String field1;
private Integer field2;
}
@Builder
@Data
@ToString
public class BasePage {
private Integer currentPage;
private Integer pageSize;
}
3 案例实践
3.1 案例:将JSON字符串解析为JSONObject | JSON协议
String text = "...";
JSONObject data = JSON.parseObject(text);
byte[] bytes = ...;
JSONObject data = JSON.parseObject(bytes);
3.2 案例:将JSON字符串解析为JSONArray | JSON协议
String text = "...";
JSONArray data = JSON.parseArray(text);
3.3 案例:将JSON字符串解析为Java对象 | JSON协议
String text = "...";
User data = JSON.parseObject(text, User.class);
3.4 案例:将Java对象序列化为JSON字符串 | JSON协议
Object data = "...";
String text = JSON.toJSONString(data);
byte[] text = JSON.toJSONBytes(data);
3.5 案例:使用JSONObject、JSONArray | JSON协议
String jsonText = "{\"id\": 2,\"name\": \"fastjson2\"}";
JSONObject obj = JSON.parseObject(jsonText);
int id = obj.getIntValue("id");
String name = obj.getString("name");
String text = "[2, \"fastjson2\"]";
JSONArray array = JSON.parseArray(text);
int id = array.getIntValue(0);
String name = array.getString(1);
3.6 案例:将Java对象序列化为JSONB字节数组 | JSONB协议
byte [] jsonBytes = JSONB.toBytes(new BasePage(1,10) )
//[-90, 84, 99, 117, 114, 114, 101, 110, 116, 80, 97, 103, 101, 1, 81, 112, 97, 103, 101, 83, 105, 122, 101, 10, -91]
3.7 案例:将JSONB字节数组序列化为JSON字符串 | JSONB协议
byte [] jsonBytes = JSONB.toBytes(new BasePage(1,10) )
//[-90, 84, 99, 117, 114, 114, 101, 110, 116, 80, 97, 103, 101, 1, 81, 112, 97, 103, 101, 83, 105, 122, 101, 10, -91]
String jsonString = JSONB.toJSONString( jsonBytes );
/**
{
"currentPage":1,
"pageSize":10
}
**/
3.8 案例:将JSONB字节数组反序列化为JSONObject/JSONArray、Java对象 | JSONB协议
byte [] jsonBytes = JSONB.toBytes(new BasePage(1,10) );
//[-90, 84, 99, 117, 114, 114, 101, 110, 116, 80, 97, 103, 101, 1, 81, 112, 97, 103, 101, 83, 105, 122, 101, 10, -91]
BasePage [] pages = new BasePage [] { new BasePage(1,10) };
byte [] jsonArrayBytes = JSONB.toBytes(pages );
//[-107, -90, 84, 99, 117, 114, 114, 101, 110, 116, 80, 97, 103, 101, 1, 81, 112, 97, 103, 101, 83, 105, 122, 101, 10, -91]
JSONObject jsonObject = JSONB.parseObject( jsonBytes ); //反序列化为 JSONObject
JSONObject jsonObject = JSONB.parse( jsonArrayBytes ); //反序列化为 JSONObject (方式2)
JSONArray jsonArray = JSONB.parseArray( jsonArrayBytes ); //反序列化为 JSONArray
BasePage page = JSONB.parseObject( jsonBytes , BasePage.class );//反序列化为 Java对象
3.9 案例:JSON Schema
JSON Schema
可用于反序列化时对JSON
字段进行验证使用,配置Schema
可以通过@JSONField
/@JSONType
,这里以@JSONField
为例
public class Entity {
private String field1;
@JSONField(schema = "{'minimum':0,'maximum':100}")
private Integer field2;
}
- Test Code
Entity entity1 = Entity.builder().field2(-1).build();
Entity entity2 = Entity.builder().field2(50).build();
Entity entity3 = Entity.builder().field2(101).build();
String str1 = JSON.toJSONString(entity1);
String str2 = JSON.toJSONString(entity2);
String str3 = JSON.toJSONString(entity3);
try {
JSON.parseObject(str1, Entity.class);
} catch (Exception e) {
e.printStackTrace();
}
JSON.parseObject(str2, Entity.class);
try {
JSON.parseObject(str3, Entity.class);
} catch (Exception e) {
e.printStackTrace();
}
3.10 案例: FastJson对调用API后的 JSON 响应内容反序列化为 Java 对象 | JSONObject/JSONArray 转 Java 对象 / Java 对象List
引入依赖
<!-- Alibaba Fastjson -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.43</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.25</version>
</dependency>
JSON 响应
调用第三方系统API的响应结果(JSON)
{
"code": 200,
"msg": null,
"data": [
{
"id": "xxx",
"deviceId": "xxxxx",
"collectTs": 1740000000352,
"identifier": "xxxxxx",
"dataType": "xxx",
"payload": "xxxxxxxxxxxxxx",
"rcvTs": 1740000008807,
"cloudMessage": {
"businessType": 2,
"configUuid": "xxxx",
"version": "52",
"type": "2",
"reissueFormat": "0",
"reportTimeStamp": "1740004000",
"len": 81,
"bodyDataset": {
"signals": {
[ ... ]
, [ ... ]
, ...
},
"fields": [
"signalName",
"signalValue",
"networkId",
"messageId",
"collectTime",
"samplingCycle"
]
},
"parseTimeConsuming": 10,
"parseStartTime": 1740000000001,
"parseEndTime": 17500000000000
}
}
,{
...
}
, ...
]
}
FastJson 反序列化
//静态变量
private final String SUCCEED = "SUCCEED";
private final String STATUS = "status";
private final String DATA = "data";
private final String ONLINE = "online";
private final String APP_KEY="?appkey=";
private final String APPKEY="&appkey=";
private final String SIGNT="&signt=";
private final String SIGN="&sign=";
//关键类
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson2.JSONArray;
Map<String, Object> objectMap = null;
objectMap = MapUtils.objectAllToMap(queryDeviceDto);
long signt = System.currentTimeMillis();
String sign = SignUtil.hmacsha1Sign(thirdPlatformConfig.getAppKey(), thirdPlatformConfig.getSecurityKey(), signt);
StringBuilder urlBuilder = new StringBuilder(thirdPlatformConfig.getAddress() + thirdPlatformConfig.getQueryVehicleUrl());
String url = urlBuilder .append("?appkey=").append(thirdPlatformConfig.getAppKey()).append("&signt=").append(signt).append("&sign=").append(sign).toString();
String res = HttpUtil.createPost(url).body(JSON.toJSONString(objectMap)).header("content-type", "application/json").execute().body();
JSONObject response = JSON.parseObject(res);
if (!SUCCEED.equals(response.getString(STATUS)) || CollUtil.isEmpty(response.getJSONArray(DATA))) {
return CollUtil.list(false);
}
JSONArray jsonArray = response.getJSONArray(DATA);
List<DeviceDto> result = jsonArray.toJavaList(DeviceDto.class);//public <T> List<T> toJavaList(Class<T> clazz, JSONReader.Feature... features)
Y FAQ
Q: Fastjson2 序列化原理
序列化的实现可以参考官方的一张类图:
大致流程:
- 获取
ObjectWriter
- 如果从
ObjectWriterProvider
缓存有ObjectWriter
,直接提取 - 如果
ObjectWriterProvider
缓存没有ObjectWriter
,构造对应的ObjectWriter
,并缓存 - 获取到
ObjectWriter
后,将JavaBean
对象写入JSONWriter
JSONWriter
对基础类型进行写入- 返回结果
Q: Fastsjon 会调用应用程序中自定义的以 get
为前缀的 getXxxx 方法!【避坑】
- 结论: Fastsjon 会调用应用程序中自定义的以
get
为前缀的 getXxxx 方法!【避坑】
方法一:自定义的方法避免定义为
get
开头。
方法二:使用@JSONField(serialize = false)
在getValue
方法上,让 fastjson 忽略该方法。
- 推荐文献
使用
JSON.toJSONString
时报 错:com.alibaba.fastjson2.JSONException: level too large : 2048
是序列化的对象有 非set/get
方法,把 非get
、set
方法 设置为不序列化就行了 :@JSONField(serialize = false)
Q: com.alibaba.fastjson2.writer.ObjectWriterImplEnum#write
com.alibaba.fastjson2.writer.ObjectWriterImplEnum#write | version=2.0.3
当待JSON序列化的类对象的枚举类属性字段的值为null时:
- 当用户没有自定义
NameFilter
时,序列化结果中枚举属性字段将被序列化为:"null":
。- 当用户序列化时在
com.alibaba.fastjson2.JSON.toJSONString(executeOperationResult, new MyFilter())
调用重新自定义的NameFilter
时,com.alibaba.fastjson2.writer.ObjectWriterImplEnum#write
方法将因值为null而在com.alibaba.fastjson2.writer.ObjectWriterAdapter#writeWithFilter
将报空指针异常。
注:官方
2.0.52
版中已修复此问题
Exception in thread "main" java.lang.NullPointerException
at com.alibaba.fastjson2.writer.ObjectWriterImplEnum.write(ObjectWriterImplEnum.java:100)
at com.alibaba.fastjson2.writer.ObjectWriterAdapter.writeWithFilter(ObjectWriterAdapter.java:399)
at com.alibaba.fastjson2.writer.ObjectWriter_2.write(Unknown Source)
at com.alibaba.fastjson2.JSON.toJSONString(JSON.java:802)
at com.xxxx.datasource.datasourcex.connector.XX.main(XX.java:19)
Q : JSONB 协议与 JSON 协议的区别?
-
json是保存为文本格式的
-
jsonb是保存为二进制格式的
-
这主要有三方面的影响:
- jsonb通常比json占用更多的磁盘空间(有些情况不是)
- jsonb比json的写入更耗时间
- json的操作比jsonb的操作明显更耗时间(在操作一个json类型值时需要每次都去解析)
详情参见:
X 参考文献
- 阿里Fastjson2强势发布,快看看它有什么改变 - 百度
- Fastjon2他来了,性能显著提升,还能再战十年 - CSDN
- 安全问题我们需要重视,立刻升级fastjson2 - 百度
- Fastjson2基础使用以及底层序列化/反序列化实现探究 - 博客园 【推荐】
Fastjson 的原理 / JSONPath的使用 等
- maven
- github

本文链接: https://www.cnblogs.com/johnnyzen
关于博文:评论和私信会在第一时间回复,或直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
日常交流:大数据与软件开发-QQ交流群: 774386015 【入群二维码】参见左下角。您的支持、鼓励是博主技术写作的重要动力!