Fastjson2 JSONB 用法

在处理大规模JSON数据时,你是否遇到过序列化性能瓶颈?阿里巴巴Fastjson2带来的JSONB二进制协议,和Fastjson1相比,性能有非常大的提升,体积减少30%~40%。下面介绍Fastjson2 JSONB的使用方法。

一、什么是JSONB?为什么需要它?

1.1 Fastjson2与JSONB简介

Fastjson2是阿里巴巴开源的高性能JSON处理库,是Fastjson 1.x的完全重构版本。与1.x相比,Fastjson2在性能、安全性和功能上都有质的飞跃,同时彻底解决了1.x中长期存在的autoType安全漏洞。

Fastjson2最吸引人的特性之一,是用同一套简洁的API同时支持传统的文本JSON和高效的二进制JSONB。这意味着你不需要为了性能而学习一套全新的API,迁移成本极低。

1.2 JSONB的核心性能优势

JSONB(JSON Binary)是一种二进制编码格式,它将数据从{"name":"张三","age":25}这样的文本字符串,转换为紧凑的字节序列。它带来了三大核心优势:

1. 体积更小、传输更快
文本JSON中的属性名、冒号、引号、括号都会占用字节。JSONB用简短的“类型标记”和“引用ID”替代冗长的字符串,一个包含1000个简单对象的数组,JSONB体积比普通JSON小约30%~40%

2. 解析速度成倍提升
文本JSON解析需要词法分析、语法分析等步骤;而JSONB是二进制格式,结构信息直接“告诉”了解析器,跳过了大量字符串处理逻辑。

3. 内存效率更高
二进制格式在内存中表示更紧凑,创建的对象更少,有助于减少GC压力,对Android应用或长期运行的Java服务至关重要。

适用场景速览

  • 高并发接口: 强烈推荐。JSONB序列化速度更快、数据体积更小,能有效降低接口延迟和网络开销。
  • 移动端App:推荐。JSONB体积减少30%~40%,可显著降低移动网络流量消耗。*
  • 大数据处理:推荐。批量数据场景下,JSONB能提升解析吞吐量,减少GC压力。*
  • 简单配置文件: 普通JSON即可。配置文件通常读取频率低、体积小,无需引入二进制格式。
  • 日志记录: 文本JSON更友好。日志需要人工可读、支持grep检索,文本格式更方便排障。

从2.0.48版本开始,官方持续优化JSONB.toBytesJSONB.parseObject的性能,JSONB在数值处理、集合类型等方面的能力不断增强。

二、JSONB环境搭建(Maven/Gradle)

2.1 Maven依赖配置

<dependency>
    <groupId>com.alibaba.fastjson2</groupId>
    <artifactId>fastjson2</artifactId>
    <version>2.0.61</version>
</dependency> 

2.2 Gradle依赖配置

dependencies {
    implementation 'com.alibaba.fastjson2:fastjson2:2.0.61'
} 

2.3 核心导入说明

Fastjson2的包名是com.alibaba.fastjson2,与1.x的com.alibaba.fastjson完全不同。核心类如下:

import com.alibaba.fastjson2.JSON;        // 推荐优先使用
import com.alibaba.fastjson2.JSONB;       // JSONB专用
import com.alibaba.fastjson2.JSONObject;  // 手动解析
import com.alibaba.fastjson2.JSONArray;   // JSON数组 

注意不要和1.x的包名混用,否则会报编译错误。

三、JSONB核心用法与技巧

3.1 序列化与反序列化

Java对象、 JSONB字节数组互转例子如下:

// 定义User类
@Data
public class User {
        private Long id;
        private String name;
        private Integer age;
        private List<String> tags;

}

// 调用方法
// ===== 序列化:Java对象 → JSONB字节数组 =====
User user = new User();
user.setId(1001L);
user.setName("张三");
user.setAge(28);
user.setTags(Arrays.asList("Java", "Fastjson2"));

User user2 = new User();
user2.setId(1002L);
user2.setName("李四");
user2.setAge(30);
user2.setTags(Arrays.asList("PHP", "Laravel"));

// 方式1:基础用法
byte[] jsonbBytes = JSONB.toBytes(user);

// 方式2:带格式化配置
byte[] bytes = JSONB.toBytes(user, JSONWriter.Feature.PrettyFormat);

// ===== 反序列化:JSONB字节数组 → Java对象 =====
// 基于Class
User deserializedUser = JSONB.parseObject(jsonbBytes, User.class);

// ===== 集合类型示例 =====
List<User> users = Arrays.asList(user, user2);
byte[] listBytes = JSONB.toBytes(users);
List<User> parsedList = JSONB.parseArray(listBytes, User.class);

// 基于TypeReference(处理泛型)
List<User> userList2 = JSONB.parseObject(listBytes, new TypeReference<List<User>>() {});

3.2 字符串与JSONB互转

如果你的JSONB数据来自日志分析或跨语言调用,现在有一个极其有用的模式,看下面的例子

private static byte[] loadFromMessageQueue() {
        // 实际场景中:从 RocketMQ、Kafka、RabbitMQ 等 MQ 接收的 byte[]
        // 这里模拟一个 JSONB 字节数组的构造过程
        // 先造一个对象,序列化为 JSONB
        JSONObject original = new JSONObject();
        original.put("id", 1001);
        original.put("name", "张三");
        original.put("age", 28);
        // 转为 JSONB 字节数组(模拟 MQ 收到的消息体)
        return JSONB.toBytes(original);
    }
// 调用方法
// 1. 将普通JSON字符串转为JSONB字节数组
String normalJson = "{\"id\":1001,\"name\":\"张三\",\"age\":28}";
byte[] jsonbBytes = JSONB.toBytes(normalJson);

// 2. 再将JSONB转回可读的JSONObject
byte[] jsonbData = loadFromMessageQueue(); // 从MQ接收的二进制数据
JSONObject obj = JSONB.parseObject(jsonbData);
String name = obj.getString("name");       // "张三"
System.out.println(name); // 输出张三

3.3 JSONB序列化特性配置

Fastjson2通过位运算管理特性开关,支持细粒度控制:

import com.alibaba.fastjson2.JSONWriter;
import com.alibaba.fastjson2.JSONReader;
import com.alibaba.fastjson2.JSONB;

// 写入特性配置
byte[] bytes = JSONB.toBytes(user, 
    JSONWriter.Feature.WriteMapNullValue,   // 输出null值字段
    JSONWriter.Feature.PrettyFormat,        // 美化格式
    JSONWriter.Feature.WriteClassName       // 写入类型信息(安全使用)
);

// 读取特性配置
User user = JSONB.parseObject(bytes, User.class,
    JSONReader.Feature.SupportAutoType,     // 支持@type(需配合白名单)
    JSONReader.Feature.FieldBased           // 字段优先访问
); 

安全提醒SupportAutoType默认关闭。如需开启,务必配合setAcceptClassNames设置严格的白名单:

JSONReader.Context context = JSONReader.Context.of();
context.setAcceptClassNames("com.example.model.*", "java.time.*");
User user = JSONB.parseObject(bytes, User.class, context); 

更安全的做法:避免使用autoType,改用@JSONType(seeAlso = {...})或在反序列化时传入TypeReference

说明:官方正在分批开发更安全和更方便使用的API选项,以覆盖更强的复杂对象和第三方商业应用场景。

3.4 Java对象JSONBJSON文本调试

开发调试时,常需要查看JSONB结构或快速验证结果:

// JSON文本转JSONB(用于性能对比或直接生成二进制负载)
byte[] binaryFromString = JSONB.toBytes(jsonString);

// JSONB转JSON文本(输出可读结构,便于调试序列化是否符合预期)
String debugJson = JSONB.toJSONString(jsonbBytes);
System.out.println("JSONB deserialized to JSON: " + debugJson);

// JSONB转Java对象一步到位
User restored = JSONB.parseObject(jsonbBytes, User.class); 

四、JSONB与传统JSON对比实战

4.1 直接对比测试

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONB;

// 构造测试数据
List<User> userList = buildLargeUserList(10000);

// === 传统JSON序列化 ===
long startJson = System.currentTimeMillis();
byte[] jsonBytes = JSON.toJSONBytes(userList);           // 注意:byte[]用作对比
long jsonTime = System.currentTimeMillis() - startJson;
int jsonSize = jsonBytes.length;

// === JSONB序列化 ===
long startJsonb = System.currentTimeMillis();
byte[] jsonbBytes = JSONB.toBytes(userList);
long jsonbTime = System.currentTimeMillis() - startJsonb;
int jsonbSize = jsonbBytes.length;

// 对比结果
System.out.println("JSON 序列化耗时: " + jsonTime + "ms, 大小: " + jsonSize + "B");
System.out.println("JSONB序列化耗时: " + jsonbTime + "ms, 大小: " + jsonbSize + "B");
System.out.println("体积缩小: " + (jsonSize - jsonbSize) * 100.0 / jsonSize + "%"); 

4.2 性能优化最佳实践

 推荐做法:

  • 批量数据处理:JSONB的反序列化速度优势在批量数据下更加明显,推荐处理超过500条记录时使用。
  • 高频调用接口:高并发场景下,使用JSONB可显著降低序列化延迟和GC压力。
  • 内存缓存场景:JSONB字节数组比字符串更紧凑,适合存入内存缓存如Caffeine/Guava Cache。

不适用或需谨慎场景:

  • 需要直接给前端返回JSON文本的API接口 → 保持使用传统JSON。
  • 日志文件存储和检索 → 文本格式更适合grep和人工查看。
  • 跨语言交换时,确保对方语言有对应的JSONB实现(目前Java生态为主,最好提前评估)。

五、JSONB与Redis集成

将JSONB与Redis结合,是提升缓存系统性能的经典场景。相比于用String存储普通JSON,JSONB能够显著减少网络传输和内存开销:

import com.alibaba.fastjson2.JSONB;
import redis.clients.jedis.Jedis;

public RedisJsonbService {
    private Jedis jedis;

    // 写入Redis(JSONB格式)
    public void set(String key, Object obj) {
        byte[] jsonbBytes = JSONB.toBytes(obj);
        jedis.set(key.getBytes(), jsonbBytes);
    }

    // 从Redis读取(JSONB格式)
    public <T> T get(String key, Class<T> clazz) {
        byte[] jsonbBytes = jedis.get(key.getBytes());
        if (jsonbBytes == null) return null;
        return JSONB.parseObject(jsonbBytes, clazz);
    }

    // 批量写入示例
    public void batchSet(Map<String, User> userMap) {
        for (Map.Entry<String, User> entry : userMap.entrySet()) {
            jedis.set(entry.getKey().getBytes(), JSONB.toBytes(entry.getValue()));
        }
    }
} 

Spring Data Redis整合(可选配置):当使用RedisTemplate时,可以创建自定义的RedisSerializer<Object>,内部调用JSONB.toBytes()JSONB.parseObject(),将默认的JDK或Jackson序列化替换为JSONB实现。

六、常见问题FAQ

1. 泛型反序列化失败怎么办?

//  错误方式:运行时泛型被擦除导致类型丢失
List<User> users = JSONB.parseObject(bytes, List.class);

// 正确方式:使用TypeReference传递完整类型信息
List<User> users = JSONB.parseObject(bytes, new TypeReference<List<User>>() {}); 

2. Timestamp和日期格式处理

从2.0.48版本开始,对Timestamp类型的JSONB解析报错问题已被修复。对于日期格式,推荐设置全局格式:

// 全局配置
JSON.configDateFormat("yyyy-MM-dd HH:mm:ss");
// 或使用注解
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date createTime; 

3. 精度丢失或解析错误

BigDecimal在小数位较多时必须使用UseBigDecimalForDoubles配置:

// 金融场景强制使用BigDecimal
JSON.config(JSONReader.Feature.UseBigDecimalForDoubles, true);
BigDecimal amount = JSONB.parseObject(bytes, BigDecimal.class);

// BigInteger解析错误通常由数值溢出引起,2.0.48+支持Feature.NonErrorOnNumberOverflow避免报错
JSON.config(JSONReader.Feature.NonErrorOnNumberOverflow, true); 

注意:2.0.53_preview曾发现INT64类型解析BigDecimal的bug,实际生产请直接升级到2.0.48+的General Availability版本。

4. 集合类型反序列化报错

确保目标集合类型有public无参构造器或使用ArrayList/HashSet等标准实现。从2.0.48开始,Set类型反序列化问题已大幅改进。

5. 枚举类型序列化错误

检查枚举是否被定义为内部static。

6. Debug疑难排查

使用JSONB.toJSONString(jsonbBytes)将二进制数据转回可读JSON格式,帮助确认序列化是否正确。

七、总结与最佳实践

特性JSONB表现
序列化速度比JSON提升2倍以上
数据体积比JSON减少30%~40%
API一致性与JSON.toJSONString语法完全一致
内存效率创建对象更少,GC压力小

总结

  1. 优先使用JSONB:通过JSONB.toBytes()JSONB.parseObject()完成核心操作,语法与JSON类完全一致,零学习成本
  2. 批量数据必用JSONB:超过500条记录时性能差异显著
  3. 开启必要特性:根据场景配置WriteMapNullValuePrettyFormat等特性
  4. 类型安全处理:泛型场景使用TypeReference,集合场景使用parseArray
  5. 安全底线:非必要不开启SupportAutoType,开启时必须设置白名单
  6. 版本升级建议:建议使用2.0.48及以上版本,以获得完整的JSONB功能支持和性能优化

Fastjson2 JSONB为Java开发者提供了同一套熟悉的API,换来2倍以上的解析速度和30%以上的体积缩减。无论你的项目是高并发服务端,还是移动端App,升级到Fastjson2并引入JSONB,都是值得投入的。