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 缓存。
注意:性能优势往往伴随着对底层细节的激进优化,这也是早期安全漏洞频发的原因之一。
性能优化最佳实践
使用
SerializeConfig和ParserConfig缓存:
不要每次调用都创建新的配置对象,它们内部维护了 Bean 的序列化/反序列化映射缓存,应作为单例使用。关闭循环引用检测(如果确定没有循环引用):
Fastjson 默认会检测循环引用(生成@type或$ref),这会消耗性能。JSON.toJSONString(obj, SerializerFeature.DisableCircularReferenceDetect);使用
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。

