Fastjson中的JSON.parseObject()详细讲解
Fastjson中的JSON.parseObject()详细讲解
前言
在 Java 开发尤其是 Web 后端开发中,我们每天都在和前后端的数据交互打交道。而这其中,出场率最高、也最容易让人踩坑的 API 之一,绝对有阿里巴巴 Fastjson 库中的 JSON.parseObject()。
在使用 Fastjson(或其他 JSON 解析库)进行日常开发时,解析普通对象往往手到擒来。但一旦遇到多层嵌套的对象或泛型集合,各种 ClassCastException 就会接踵而至。
很多开发者只知道去搜一行 TypeReference 的代码贴上去解决问题,却不明白底层到底发生了什么。今天,我们就用循序渐进的实战代码,结合底层 JVM 的“类型擦除”机制,彻底弄清楚JSON.parseObject().
一、JSON.parseObject()是什么
JSON.parseObject() 的本质是一个“极速翻译官”。
在互联网传输中,数据必须以**纯文本(字符串)**的形式流动,比如 {"name": "Alice", "age": 20}。但是,Java 是一门面向对象的强类型语言,它只认识 对象(Object) 。
JSON.parseObject() 的工作,就是读取这段字符串,然后在 Java 的内存中创造出一个对应的、活生生的 Java 对象,把数据严丝合缝地塞进去。
这个过程在计算机科学中被称为反序列化(Deserialization)。

1.0 json是什么
JSON本质就是一种“有结构的字符串格式”,用来表示数据。
1. 格式规范
- key 必须加双引号
1 | { "name": "Alice" } ✅ |
- 字符串必须用双引号
1 | "name": "Alice" ✅ |
- 不能有多余逗号
1 | { |
2. JSON常见类型如下:
JSON 中
{}表示对象(必须是键值对结构),[]表示数组(可包含多个元素)。
如果需要表达多个对象,必须使用数组,而不能在{}中直接堆叠对象。
- 对象(Object)——用
{}表示
类似 Java 中的
Map<String, Object>1
2
3
4
5{
"name": "Alice",
"age": 20,
"isStudent": true
}特点:
用
{}包裹数据是 键值对(key-value)
key 必须是字符串(必须加双引号)
- 数组(Array)——用
[]表示
类似 Java 中的
List1
2
3
4
5[
"Apple",
"Banana",
"Orange"
]特点:
用
[]包裹里面是有序数据集合
每个元素可以是任意类型
JSON的嵌套结构
JSON最大的特点是:可以无限嵌套1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16{
"name": "Agent",
"actions": [
{
"actionName": "Click",
"target": "ButtonA"
},
{
"actionName": "Input",
"target": "SearchBox"
}
],
"scores": {
"confidence": 0.95
}
}这对应 Java 结构:
1
2
3
4
5class Agent {
String name;
List<Action> actions;
Map<String, Double> scores;
}
1.1 转成通用的 JSONObject
不想写实体类,或者外部传来的 JSON 字段很不稳定,只需要提取其中一两个值的时候,用这种方式最合适。
深度理解: JSONObject 底层其实就是实现了 Map<String, Object> 接口。它相当于给你提供了一个万能容器,不管 JSON 长什么样都能装下。
1 | import com.alibaba.fastjson.JSON; |
1.2 转成自定义的 Java 实体类
这是企业级开发中最规范的做法。你需要明确指定一个“模具”(Class),让 Fastjson 按照这个模具去浇筑对象。
为 JSON 建立对应的 Java 实体类,有3个要点:
- 必须保留公共无参构造函数(Fastjson 靠它通过反射实例化对象)。
- 嵌套类尽量使用
public static class(静态内部类),否则 Fastjson 无法独立实例化它。 - 必须提供标准的 Getter/Setter(可以使用 Lombok 的
@Data注解)。
1 | public class User { |
1.3 JSON.parseObject 的反射原理解析
执行 JSON.parseObject(json, User.class) 时,Fastjson 底层借助java的反射机制实现json字符串和类对象的转换。原理如下:
词法解析 (Lexical Analysis -
JSONLexer)Fastjson 不会一开始就去管你的 Java 类。它首先像编译器一样,逐个字符读取 JSON 字符串,将其拆解成一个个 Token(标识符)。
比如遇到
{就知道是一个对象的开始,遇到"name"就知道是一个 Key。
获取元数据 (Metadata &
JavaBeanInfo)解析器拿到目标类
User.class后,会启动反射引擎。它通过
Class.getDeclaredFields()和Class.getMethods()扫描这个类的所有结构。它会重点寻找:无参构造函数、所有的 Setter 方法。
为了极致的性能,Fastjson 会把这些反射拿到的类结构信息缓存起来(存入
ParserConfig的 ConcurrentHashMap 中),下次再解析同一个类时,直接从内存取,不再进行耗时的反射扫描。
实例化空对象 (Instantiation)
- 通过反射拿到无参构造函数后,执行
constructor.newInstance(),在内存中创造出一个全是 null 值的空对象。
- 通过反射拿到无参构造函数后,执行
类型推导与反序列化 (Deserialization)
读取 JSON 里的键值对。假设读到
"age": "18"。通过前面缓存的元数据,发现
User类里有一个setAge(Integer age)方法。此时触发类型转换 (TypeUtils):JSON 里是字符串
"18",目标方法要的是Integer,Fastjson自动将其转换为整数18。
动态注入 (Method.invoke)
- 最后一步,通过反射机制执行
method.invoke(userInstance, 18),把值硬塞进刚才创建的空对象里。整个对象拼装完成。
- 最后一步,通过反射机制执行

1.4 经典问题:如何解析泛型集合
1 | String jsonArrayStr = "[{\"name\":\"李白\"}, {\"name\":\"杜甫\"}]"; // 一个列表 |
要用 parseObject 解析极其复杂的结构,必须加上大括号 {} 创建匿名内部类,把泛型写进去。
关于
parseObject解析极其复杂的结构,Fastjson 祭出了大杀器:TypeReference。既然运行时的对象记不住泛型,那我们就“现场造一个带泛型基因的新图纸”。
代码末尾的
{}表示我们在现场创建了一个继承自TypeReference的匿名内部类。Java 规定,类的父类泛型信息会永久保留在字节码中。Fastjson 拿到这个匿名类后,顺藤摸瓜查它的“族谱”(父类),就能找回完整的<List<AgentResponse>>基因信息!
1.5 Java 的“类型擦除”
对于比较老的java版本,这个写法可以通过编译,但是依然没有实现json到类对象的转化,原因是java的类型”擦除”:
1 | List<User> list = JSON.parseObject(jsonArray, List.class); |
这里传给 Fastjson 的目标类型是 List.class,在运行时,泛型 <User> 被彻底擦除了,Fastjson 只知道你要一个 List,但不知道里面装什么,于是只能按最通用的 JSONObject 来兜底。
假设 Java 的世界是一个大型的物流中心。
泛型(比如
List<AgentResponse>)是什么?它就像是贴在普通快递纸箱外面的一张便利贴,上面写着:“这里面只能装 AgentResponse!”
- 在发货前(写代码、编译时): Java 编译器(安检员)会死死盯着这张便利贴。如果你试图往纸箱里塞一个苹果(比如
String),安检员会立刻报错,阻止你发货。这就是泛型最大的作用:在写代码时保证类型安全。- 在运输途中(程序运行起来时,即 Runtime): Java 有一个极其坑爹的机制叫做“类型擦除”。为了省事和兼容老版本的 Java,纸箱一旦装上车(程序跑起来了),外面的那张便利贴就会被撕掉!
这就是真相:在程序真正运行的那一刻,所有的
List<AgentResponse>、List<String>、List<User>,在 JVM(Java虚拟机)眼里,全都变成了一个光秃秃的普通纸箱 —— 仅仅只是一个List。Fastjson的设计流程如下:
- Fastjson 拿到了 JSON 字符串:
[{"intent":"search"}]。- Fastjson 看到了你传进来的目标类型:
List.class。- Fastjson 问 Java 虚拟机(JVM):“老哥,他让我解析成一个
List,但这纸箱里面到底该装什么类型的对象啊?”- JVM 摊摊手:“不知道啊,便利贴(泛型)在程序一跑起来就被撕掉了(擦除了),我只知道它是个纸箱(List)。”
- Fastjson 无奈了:“既然不知道里面装啥,那我只能随便拿个最基础的容器给你兜着了。”于是,Fastjson 把 JSON 里的每一个
{}都变成了通用的JSONObject(相当于一个 Map),统统扔进了 List 里。最终,你拿到的
list里面,装的全是JSONObject,而不是你想要的User。
**
二、代码实战
测试源码链接 :https://github.com/likerhood/CodeDesignWork/tree/main/codedesign0.0-0
2.1 准备测试数据
准备一份模拟外部 AI Agent 系统传来的 JSON 数据。
这是一个包含复杂嵌套(对象内部有 List 和 Map)的 JSON 数组:
1 | [ |
2.2 实体类定义
我们需要为上面的 JSON 建立对应的 Java 实体类,注意以下三点:
- 必须保留公共无参构造函数
- 嵌套类尽量使用
public static class(静态内部类) - 必须提供标准的 Getter/Setter(这里为了代码直观,使用了 Lombok 的
@Data注解)。
1 | import lombok.Data; |
2.3 解析实体类内部的泛型集合
疑惑点: Java 运行时会发生“类型擦除”,那 AgentResponse 内部的 List<Action> 里的泛型 <Action> 会被擦除吗?Fastjson 会不会解析失败?
结论: 不会!完美解析。
底层原理: Java 的类型擦除只擦除内存中实例对象的泛型,绝对不会擦除类结构声明上的泛型.编译器会把 List<Action> 这个泛型签名死死地烙印在 .class 字节码文件中。Fastjson 底层通过 Field.getGenericType() ,查阅字节码文件,就能精准得知必须装入 Action 对象。
1 | import com.alibaba.fastjson.JSON; |
运行结果:成功输出,没有任何报错。
2.4 直接解析顶层匿名泛型集合
💡 疑惑点: 如果我们直接把刚才那个整体的 JSON 数组传进去,并试图用 List.class 去接,会发生什么?
1 | import com.alibaba.fastjson.JSON; |
原因: 当我们在方法参数中传递
List.class时,程序已经运行起来了。此时,JVM 遵循“类型擦除”,把泛型便利贴无情撕掉。Fastjson 问 JVM:“这个 List 里面装什么?”JVM答:“不知道,就个普通 List。”既然不知道,
Fastjson只能用最基础的通用字典JSONObject来兜底。所以,list.get(0)拿到的根本不是AgentResponse,而是一个JSONObject!强转必定报错。
2.5 TypeReference 终极破解
代码末尾的 {} 表示我们在现场创建了一个继承自 TypeReference 的匿名内部类。
Java 规定,类的父类泛型信息会永久保留在字节码中。
Fastjson 拿到这个匿名类后,顺藤摸瓜查它的“族谱”(父类),就能找回完整的 <List<AgentResponse>> 基因信息!
1 | import com.alibaba.fastjson.JSON; |
总结
JSON.parseObject() 本质是通过 反射 + 类型推导 将 JSON 字符串还原为 Java 对象。
在实际开发中,问题的核心并不在 API 本身,而在于 Java 的类型机制:
- 普通对象解析:依赖无参构造 + Setter,按字段映射即可完成
- 类内部泛型:泛型信息保存在字节码中,Fastjson 可通过反射获取,解析无压力
- 顶层泛型集合:由于类型擦除,运行时丢失泛型信息,必须借助
TypeReference补全类型
👉 一句话记住:
能解析 ≠ 写对了,关键在于“类型信息是否在运行时可获取”