Fastjson1 快速上手

为什么选择 Fastjson 1.x?

Fastjson 是阿里巴巴开源的 JSON 库,在 Java 生态中拥有极高的市场占有率。虽然 Fastjson2 已经推出,但 Fastjson 1.x(特别是 1.2.83+ 版本)依然被大量存量项目使用。

  • 极速性能:在很长一段时间内,Fastjson 1.x 凭借其自研的解析算法(基于 ASM 技术),在序列化和反序列化速度上优于 Jackson 和 Gson。
  • 使用简单:核心 API 极其精简,JSON 工具类几乎能满足所有需求,上手成本极低。
  • 功能丰富:内置了对泛型、日期格式、循环引用等复杂场景的支持,无需繁琐配置。

安全性机制详解

由于 Fastjson 1.x 历史上曾出现过反序列化安全漏洞(AutoType 漏洞),在使用时必须关注安全配置。

  • 升级版本:务必使用 1.2.83 或更高版本,该版本对安全机制进行了重构。
  • AutoType 机制:Fastjson 允许在 JSON 字符串中通过 @type 指定类名进行反序列化。在 1.2.83+ 版本中,AutoType 默认开启但受限,建议显式配置白名单。

安全配置示例:

// 开启 AutoType 支持并添加白名单(推荐做法)
ParserConfig.getGlobalInstance().addAccept("com.example.domain.");
// 或者在反序列化时关闭 AutoType(最安全,但无法处理多态)
// JSON.parseObject(jsonStr, Object.class, Feature.DisableSpecialKeyDetect); 

环境准备 Maven

pom.xml 中引入 Fastjson 1.x 的依赖。请务必锁定版本号以确保稳定性。

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.83</version>
</dependency> 

基础用法:序列化与反序列化

1. 序列化(对象 → JSON)

将 Java 对象转换为 JSON 字符串,最常用的方法是 JSON.toJSONString()

User user = new User(1, "Alice", 23);

// 默认序列化
String json = JSON.toJSONString(user);
// 输出: {"age":23,"id":1,"name":"Alice"}

// 格式化输出(PrettyFormat),便于调试查看
String prettyJson = JSON.toJSONString(user, SerializerFeature.PrettyFormat); 

2. 反序列化(JSON → 对象)

将 JSON 字符串还原为 Java 对象,使用 JSON.parseObject()

String jsonStr = "{\"id\":1,\"name\":\"Alice\",\"age\":23}";

// 转换为 JavaBean
User user = JSON.parseObject(jsonStr, User.class);

// 转换为 JSONObject(如果不定义类,可作为 Map 处理)
JSONObject jsonObject = JSON.parseObject(jsonStr);
String = jsonObject.getString("name"); 

3. 处理集合与泛型

处理 List<T>Map<K, V> 时,由于 Java 的泛型擦除,不能直接传 List.class,需要使用 TypeReference

String jsonArrayStr = "[{\"id\":1,\"name\":\"A\"},{\"id\":2,\"name\":\"B\"}]";

// 解析为 List<User>
List<User> userList = JSON.parseObject(jsonArrayStr, new TypeReference<List<User>>(){});

// 或者使用 parseArray 快捷方法
List<User> list2 = JSON.parseArray(jsonArrayStr, User.class); 

4. JSONObject / JSONArray 解析

Fastjson 提供了类似 DOM 树的解析方式,适合处理结构不固定的 JSON。

String json = "{\"data\":{\"id\":10},\"tags\":[\"java\",\"json\"]}";
JSONObject root = JSON.parseObject(json);

// 获取嵌套对象
JSONObject data = root.getJSONObject("data");
Integer = data.getInteger("id");

// 获取数组
JSONArray tags = root.getJSONArray("tags");
String firstTag = tags.getString(0); 

循环引用与 $ref 解决方案

当两个对象互相引用(A引用B,B引用A)时,Fastjson 为了防止内存溢出,默认会开启引用检测。

  • 第一次出现对象:正常输出 JSON。
  • 第二次出现对象:会被替换为 {"$ref": "..."}。

注意: 这种格式前端无法直接解析,通常被视为“脏数据”。

下面是2个解决Fastjson1的循环引用的解决方案,或者点击这里

1. 字段级屏蔽
如果前端不需要某些反向关联的字段,直接在实体类的字段上加上 @JSONField(serialize = false) 注解。
这能告诉 Fastjson 在序列化时直接无视该字段,从根源上彻底打断循环链条。

public EntityA {
    // ... 其他字段

    // 序列化时直接跳过该字段,防止死循环
    @JSONField(serialize = false) 
    private EntityB entityB; 
} 

2. 使用 DTO 数据传输对象
在 Web API 开发中,直接将带有双向引用的数据库实体(Entity)返回给前端本身就是大忌。
最稳妥的做法是创建一个专门给前端用的 DTO 对象,只保留前端需要的扁平化字段,不保留完整的对象引用。

// 将复杂的 Entity 转换为结构清晰、没有循环引用的 DTO
DTO dto = new DTO(entity.getId(), entity.getName(), entity.getOtherInfo());

// 序列化 DTO,没有任何循环引用的风险
String jsonStr = JSON.toJSONString(dto); 

$ref 机制本质上是 Fastjson 保护你的代码不崩溃的安全机制,而不是 Bug。与其强行禁用检测引发栈溢出(StackOverflowError)的风险,不如在编码规范层面避免循环引用,或者让前端适配这个标准格式。

JSONPath 支持

Fastjson 内置了强大的 JSONPath 实现,可以像 XPath 解析 XML 一样提取 JSON 数据。

String json = "{\"store\":{\"book\":[{\"price\":8.95,\"category\":\"fiction\"}]}}";

// 提取所有书的价格
List<Object> prices = (List<Object>) JSONPath.eval(json, "$.store.book[*].price");

// 判断是否存在某个路径
boolean exists = JSONPath.contains(json, "$.store.book[0].category"); 

JSONB 二进制格式

JSONB 是 Fastjson 1.2.5x 后引入的实验性二进制格式,但在 Fastjson2 中才作为核心特性。在 1.x 中支持有限,主要用于内部优化。

在 1.x 中,你可以尝试将其转为字节流以减少网络传输体积,使用较少,了解即可:

User user = new User(1L, "Alice", 23);
// 注册白名单
ParserConfig.getGlobalInstance().addAccept("com.example.domain.User");
// 序列化为二进制
byte[] jsonbBytes = JSON.toJSONBytes(user, SerializerFeature.WriteClassName);
// 反序列化
User u = (User) JSON.parse(jsonbBytes);
System.out.println(u.getName());

常用配置与特性

字段命名策略

如果 Java 字段是 userName,但 JSON 是 user_name,可以通过配置或注解解决。

 // 方式1:注解精准控制(优先级高,覆盖全局配置)
@JSONField(name = "user_name")  // 直接指定JSON字段名
private String userName;
private String email;  // 无注解字段将受全局策略影响
// 方式2:全局配置(影响所有无注解字段)
SerializeConfig config = new SerializeConfig();
config.propertyNamingStrategy = PropertyNamingStrategy.SnakeCase; // 驼峰转下划线
// 执行序列化(注解字段不受全局策略影响)
String json = JSON.toJSONString(user, config);

日期格式化

处理 java.util.Date 类型。

User user = new User();
user.setBirth(new Date());

// 方式1:全局配置
JSON.DEFFAULT_DATE_FORMAT = "yyyy-MM-dd";
String json1 = JSON.toJSONString(user, SerializerFeature.WriteDateUseDateFormat);

// 方式2:局部指定(推荐)
String json2 = JSON.toJSONStringWithDateFormat(user, "yyyy-MM-dd HH:mm:ss", SerializerFeature.WriteDateUseDateFormat); 
// 方式3:注解精准控制(优先级最高,覆盖全局配置)
@JSONField(format = "yyyy/MM/dd")  // 直接在字段声明注解
private Date birth; 

忽略空值

在默认情况下,Fastjson 是不会输出值为 null的字段的。
Fastjson 之所以采用这种默认策略,主要是为了优化 JSON 的传输效率——通过直接省略 null 字段来减小 JSON 字符串的体积,这在早期高并发、大数据量的网络通信场景中是一种常见的设计考量。

User user = new User();
user.setName("张三");
// age和email保持null

// ✅ 默认行为:自动跳过null字段(无需任何配置)
String defaultJson = JSON.toJSONString(user);
System.out.println(defaultJson);
// 输出: {"name":"张三"}  ← age/email为null被自动跳过

// ❌ 严重错误修正:以下配置实际是【输出null】而非跳过!
String wrongSkipJson = JSON.toJSONString(user,
		SerializerFeature.WriteMapNullValue, // 关键错误点
		SerializerFeature.DisableCircularReferenceDetect
);
System.out.println(wrongSkipJson);
// 实际输出: {"name":"张三","age":null,"email":null}
// 说明:
// 1. SerializerFeature.WriteMapNullValue 的真实作用是【强制输出null值】
// 2. 该配置与"跳过null"完全相反,注释中"显式指定跳过null"是严重误导
// 3. FastJSON 默认已跳过null,无需任何配置即可实现跳过

// ✅ 正确输出null的方式(显式配置)
String includeNullJson = JSON.toJSONString(user,
		SerializerFeature.WriteMapNullValue // 明确意图:输出null
);
System.out.println(includeNullJson);
// 输出: {"name":"张三","age":null,"email":null}

@JSONField 注解详解

@JSONField 注解是 Fastjson 最强大的功能之一,用于精细化控制字段。

public class User {
    // 1. 指定 JSON 中的字段名
    @JSONField(name = "user_id")
    private Integer userId;

    // 2. 控制日期格式
    @JSONField(format = "yyyy-MM-dd")
    private Date birthday;

    // 3. 序列化时忽略该字段(不输出)
    @JSONField(serialize = false)
    private String password;

    // 4. 反序列化时忽略(不赋值)
    @JSONField(deserialize = false)
    private String internalCode;

    // 5. 控制字段顺序
    @JSONField(ordinal = 1)
    private String userName;
} 
  • @JSONField(name = "user_id"):非常实用。Java 规范推荐驼峰命名(如 userId),而数据库或前端接口常用下划线命名(如 user_id),这个注解完美解决了两者转换的问题。
  • @JSONField(format = "yyyy-MM-dd"):Fastjson 处理 Date 类型时的标配,可以精确控制时间输出的格式。
  • @JSONField(serialize = false):常用于过滤敏感信息。比如你写的 password 字段,在返回给前端时绝对不能暴露,加上这个注解后,该字段在生成 JSON 时就会被直接忽略。
  • @JSONField(deserialize = false):常用于内部字段。比如 internalCode 可能只是后端内部逻辑使用,不需要也不应该由前端传参来赋值。
  • @JSONField(ordinal = 1):Fastjson 默认会按照字段名的字母顺序进行序列化。如果你希望输出的 JSON 字段有特定的阅读顺序,可以通过 ordinal 来指定,数字越小排在越前面。

实际案例:Web API 交互

在 Spring MVC 或普通 Web 项目中,通常封装一个统一的返回结果。

public Result<T> {
    private int code;
    private String msg;
    private T data;
    // getter/setter...
}

// 模拟 API 返回
Result<User> result = new Result<>();
result.setCode(200);
result.setMsg("Success");
result.setData(new User(1, "Tom", 20));

// 转换为前端需要的 JSON 字符串
String apiResponse = JSON.toJSONString(result);
// 输出: {"code":200,"data":{"age":20,"id":1,"name":"Tom"},"msg":"Success"} 

性能对比速览非严谨测试

在 Fastjson 1.x 时代,其主要特点就是快。

  • 序列化速度:Fastjson 1.x > Jackson > Gson
  • 反序列化速度:Fastjson 1.x > Jackson > Gson
  • 内存占用:Fastjson 通常表现较好,但在处理超大对象时需注意 ASM 缓存。

注意:性能优势往往伴随着对底层细节的激进优化,这也是早期安全漏洞频发的原因之一。

性能优化最佳实践

  1. 使用 SerializeConfigParserConfig 缓存
    不要每次调用都创建新的配置对象,它们内部维护了 Bean 的序列化/反序列化映射缓存,应作为单例使用。

  2. 关闭循环引用检测(如果确定没有循环引用)
    Fastjson 默认会检测循环引用(生成 @type$ref),这会消耗性能。

    JSON.toJSONString(obj, SerializerFeature.DisableCircularReferenceDetect); 
  3. 使用 WriteMapNullValue 需谨慎
    输出所有 null 值会增加网络传输量,除非前端强制要求,否则建议使用 SkipNullValue

从 Fastjson 1.x 迁移指南

虽然你使用的是 1.x,但了解它与其他库的区别也是至关重要的:

  • 与 Jackson 对比:Jackson 生态更完善(Spring Boot 默认),安全性更稳健;Fastjson 1.x API 更简单,速度更快但需关注版本漏洞。
  • 注解不兼容@JSONField (Fastjson) 与 @JsonProperty (Jackson) 不通用,迁移时需替换注解。

总结与最佳实践

Fastjson 1.x 需要注意以下几个方面:

  • 最佳实践
    • 锁定版本:生产环境必须使用 1.2.83 或以上版本。
    • 开启白名单:涉及反序列化外部输入时,务必使用 ParserConfig.getGlobalInstance().addAccept(...)
    • 善用注解@JSONField 能解决 90% 的格式适配问题。
    • 避免过度依赖:尽量使用标准的 JavaBean,避免过于复杂的嵌套泛型,以减少解析错误。

通过遵循上述规范,你依然可以在旧项目维护或新项目中安全、高效地使用 Fastjson 1.x。