[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> {

}

Fastjson2JSONObject 实现了链结构的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 方法,把 非 getset方法 设置为不序列化就行了 : @JSONField(serialize = false)

Q: com.alibaba.fastjson2.writer.ObjectWriterImplEnum#write

com.alibaba.fastjson2.writer.ObjectWriterImplEnum#write | version=2.0.3
当待JSON序列化的类对象的枚举类属性字段的值为null时:

  1. 当用户没有自定义 NameFilter时,序列化结果中枚举属性字段将被序列化为:"null":
  2. 当用户序列化时在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 参考文献

Fastjson 的原理 / JSONPath的使用 等

posted @ 2023-11-07 14:32  千千寰宇  阅读(11273)  评论(0)    收藏  举报