Fastjson中的JSON.parseObject()详细讲解

文章目录

    • 前言
    • 一、`JSON.parseObject()`是什么
      • [1.0 `json`是什么](#1.0 json是什么)
        • [1. 格式规范](#1. 格式规范)
        • [2. `JSON`常见类型如下:](#2. JSON常见类型如下:)
      • [1.1 转成通用的` JSONObject`](#1.1 转成通用的 JSONObject)
      • [1.2 转成自定义的 Java 实体类](#1.2 转成自定义的 Java 实体类)
      • [1.3 `JSON.parseObject` 的反射原理解析](#1.3 JSON.parseObject 的反射原理解析)
      • [1.4 经典问题:如何解析泛型集合](#1.4 经典问题:如何解析泛型集合)
      • [1.5 Java 的"类型擦除"](#1.5 Java 的“类型擦除”)
    • 二、代码实战
      • [2.1 准备测试数据](#2.1 准备测试数据)
      • [2.2 实体类定义](#2.2 实体类定义)
      • [2.3 解析实体类内部的泛型集合](#2.3 解析实体类内部的泛型集合)
      • [2.4 直接解析顶层匿名泛型集合](#2.4 直接解析顶层匿名泛型集合)
      • [2.5 `TypeReference` 终极破解](#2.5 TypeReference 终极破解)
    • 总结

前言

在 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. 格式规范
  1. key 必须加双引号

    { "name": "Alice" } ✅
    { name: "Alice" } ❌

  2. 字符串必须用双引号

    "name": "Alice" ✅
    "name": 'Alice' ❌

  3. 不能有多余逗号

    {
    "name": "Alice",
    "age": 20
    } ✅

    {
    "name": "Alice",
    "age": 20,
    } ❌

2. JSON常见类型如下:

JSON 中 {} 表示对象(必须是键值对结构),[] 表示数组(可包含多个元素)。

如果需要表达多个对象,必须使用数组,而不能在 {} 中直接堆叠对象。

  1. 对象(Object)------用 {} 表示
  • 类似 Java 中的 Map<String, Object>

    json 复制代码
    {
      "name": "Alice",
      "age": 20,
      "isStudent": true
    }
  • 特点:

    • {} 包裹

    • 数据是 键值对(key-value)

    • key 必须是字符串(必须加双引号)

  1. 数组(Array)------用 [] 表示
  • 类似 Java 中的 List

    json 复制代码
    [
      "Apple",
      "Banana",
      "Orange"
    ]
  • 特点:

    • [] 包裹

    • 里面是有序数据集合

    • 每个元素可以是任意类型

  1. JSON 的嵌套结构
  • JSON 最大的特点是:可以无限嵌套

    json 复制代码
    {
      "name": "Agent",
      "actions": [
        {
          "actionName": "Click",
          "target": "ButtonA"
        },
        {
          "actionName": "Input",
          "target": "SearchBox"
        }
      ],
      "scores": {
        "confidence": 0.95
      }
    }

    这对应 Java 结构:

    java 复制代码
    class Agent {
        String name;
        List<Action> actions;
        Map<String, Double> scores;
    }

1.1 转成通用的 JSONObject

不想写实体类,或者外部传来的 JSON 字段很不稳定,只需要提取其中一两个值的时候,用这种方式最合适。

深度理解: JSONObject 底层其实就是实现了 Map<String, Object> 接口。它相当于给你提供了一个万能容器,不管 JSON 长什么样都能装下。

java 复制代码
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

public class Demo1 {
    public static void main(String[] args) {
        String jsonStr = "{\"orderId\":\"O_123\", \"amount\": 99.8, \"status\":\"SUCCESS\"}";

        // 核心代码:直接转为 JSONObject
        JSONObject jsonObj = JSON.parseObject(jsonStr);

        // 像 Map 一样去取值
        String orderId = jsonObj.getString("orderId");
        Double amount = jsonObj.getDouble("amount");
        
        System.out.println("订单号: " + orderId + ", 金额: " + amount);
    }
}

1.2 转成自定义的 Java 实体类

这是企业级开发中最规范的做法。你需要明确指定一个"模具"(Class),让 Fastjson 按照这个模具去浇筑对象。

为 JSON 建立对应的 Java 实体类,有3个要点

  1. 必须保留公共无参构造函数(Fastjson 靠它通过反射实例化对象)。
  2. 嵌套类尽量使用 public static class(静态内部类),否则 Fastjson 无法独立实例化它。
  3. 必须提供标准的 Getter/Setter (可以使用 Lombok 的 @Data 注解)。
java 复制代码
public class User {
    private String name;
    private Integer age;

    // 省略 Getter 和 Setter 方法 (极其重要,后面会讲坑点)
}

public class Demo2 {
    public static void main(String[] args) {
        String jsonStr = "{\"name\": \"李白\", \"age\": 18}";

        // 核心代码:传入字符串和目标类的 Class 对象
        User user = JSON.parseObject(jsonStr, User.class);

        System.out.println("用户名: " + user.getName());
    }
}

1.3 JSON.parseObject 的反射原理解析

执行 JSON.parseObject(json, User.class) 时,Fastjson 底层借助java的反射机制 实现json字符串和类对象的转换。原理如下:

  1. 词法解析 (Lexical Analysis - JSONLexer)

    • Fastjson 不会一开始就去管你的 Java 类。它首先像编译器一样,逐个字符读取 JSON 字符串,将其拆解成一个个 Token(标识符)。

    • 比如遇到 { 就知道是一个对象的开始,遇到 "name" 就知道是一个 Key。

  2. 获取元数据 (Metadata & JavaBeanInfo)

    • 解析器拿到目标类 User.class 后,会启动反射引擎

    • 它通过 Class.getDeclaredFields()Class.getMethods() 扫描这个类的所有结构。

    • 它会重点寻找:无参构造函数、所有的 Setter 方法。

    • 为了极致的性能,Fastjson 会把这些反射拿到的类结构信息缓存起来(存入 ParserConfig 的 ConcurrentHashMap 中),下次再解析同一个类时,直接从内存取,不再进行耗时的反射扫描。

  3. 实例化空对象 (Instantiation)

    • 通过反射拿到无参构造函数后,执行 constructor.newInstance()在内存中创造出一个全是 null 值的空对象
  4. 类型推导与反序列化 (Deserialization)

    • 读取 JSON 里的键值对。假设读到 "age": "18"

    • 通过前面缓存的元数据,发现 User 类里有一个 setAge(Integer age) 方法。

    • 此时触发类型转换 (TypeUtils) :JSON 里是字符串 "18",目标方法要的是 IntegerFastjson 自动将其转换为整数 18

  5. 动态注入 (Method.invoke)

    • 最后一步,通过反射机制执行 method.invoke(userInstance, 18),把值硬塞进刚才创建的空对象里。整个对象拼装完成。

1.4 经典问题:如何解析泛型集合

java 复制代码
String jsonArrayStr = "[{\"name\":\"李白\"}, {\"name\":\"杜甫\"}]";   
// User是一个类
// ❌ 错误做法:
// 1. JSON.parseObject不知道List列表中是什么对象
List<User> list = JSON.parseObject(jsonArray, List.class);
// 2.JSON.parseObject(..., User.class) 的返回值类型是 User,提示 Incompatible types(类型不兼容),代码根本跑不起来。
List<User> list = JSON.parseObject(jsonArray, User.class);

// 正确写法
// 1. 使用 parseArray,它专门用来对付以 [ 开头的数组
List<User> userList = JSON.parseArray(jsonArrayStr, User.class);
// 2. 使用TypeReference
Map<String, List<User>> complexData = JSON.parseObject(
    "复杂的JSON串", 
    new TypeReference<Map<String, List<User>>>(){}
);

要用 parseObject 解析极其复杂的结构,必须加上大括号 {} 创建匿名内部类,把泛型写进去。

关于parseObject 解析极其复杂的结构,Fastjson 祭出了大杀器:TypeReference

既然运行时的对象记不住泛型,那我们就"现场造一个带泛型基因的新图纸"。

代码末尾的 {} 表示我们在现场创建了一个继承自 TypeReference 的匿名内部类 。Java 规定,类的父类泛型信息会永久保留在字节码中。Fastjson 拿到这个匿名类后,顺藤摸瓜查它的"族谱"(父类),就能找回完整的 <List<AgentResponse>> 基因信息!

1.5 Java 的"类型擦除"

对于比较老的java版本,这个写法可以通过编译,但是依然没有实现json到类对象的转化,原因是java的类型"擦除":

java 复制代码
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的设计流程如下:

  1. Fastjson 拿到了 JSON 字符串:[{"intent":"search"}]
  2. Fastjson 看到了你传进来的目标类型:List.class
  3. Fastjson 问 Java 虚拟机(JVM):"老哥,他让我解析成一个 List,但这纸箱里面到底该装什么类型的对象啊?"
  4. JVM 摊摊手:"不知道啊,便利贴(泛型)在程序一跑起来就被撕掉了(擦除了),我只知道它是个纸箱(List)。"
  5. 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 数组

JSON 复制代码
[
  {
    "intent": "execute",
    "actions": [
      {"actionName": "Click", "target": "ButtonA"},
      {"actionName": "Input", "target": "SearchBox"}
    ],
    "confidenceScores": {
      "execution_rate": 0.95
    }
  },
  {
    "intent": "summarize",
    "actions": [
      {"actionName": "Scroll", "target": "PageBottom"}
    ],
    "confidenceScores": {
      "summary_rate": 0.88
    }
  }
]

2.2 实体类定义

我们需要为上面的 JSON 建立对应的 Java 实体类,注意以下三点:

  1. 必须保留公共无参构造函数
  2. 嵌套类尽量使用 public static class(静态内部类)
  3. 必须提供标准的 Getter/Setter(这里为了代码直观,使用了 Lombok 的 @Data 注解)。
Java 复制代码
import lombok.Data;
import java.util.List;
import java.util.Map;

@Data
public class AgentResponse {
    private String intent;
    
    // 重点关注:这是一个包含泛型的集合属性
    private List<Action> actions; 
    private Map<String, Double> confidenceScores;

    @Data
    public static class Action {
        private String actionName;
        private String target;
    }
}

2.3 解析实体类内部的泛型集合

疑惑点: Java 运行时会发生"类型擦除",那 AgentResponse 内部的 List<Action> 里的泛型 <Action> 会被擦除吗?Fastjson 会不会解析失败?

结论: 不会!完美解析。

底层原理: Java 的类型擦除只擦除内存中实例对象 的泛型,绝对不会擦除类结构声明上的泛型 .编译器会把 List<Action> 这个泛型签名死死地烙印在 .class 字节码文件中。Fastjson 底层通过 Field.getGenericType() ,查阅字节码文件,就能精准得知必须装入 Action 对象。

Java 复制代码
import com.alibaba.fastjson.JSON;
// 1. 读取本地 test.json 文件(包含多个 Agent 响应的数组)
String jsonStr = new String(Files.readAllBytes(Paths.get("test.json")));

// 2. 先解析为 List 集合
List<AgentResponse> list = JSON.parseObject(jsonStr, new TypeReference<List<AgentResponse>>(){});

// 3. 取出第一个对象来验证"内部嵌套泛型"
AgentResponse response = list.get(0);

// 4. 核心验证:测试能否正常拿取内部嵌套的 Action 对象
// 如果泛型被擦除,这里拿到的会是 JSONObject,调用 getActionName() 会直接报错
System.out.println("外层意图: " + response.getIntent());
System.out.println("内层第一个动作名: " + response.getActions().get(0).getActionName());
System.out.println("内层第一个动作目标: " + response.getActions().get(0).getTarget());

运行结果:成功输出,没有任何报错。

2.4 直接解析顶层匿名泛型集合

💡 疑惑点: 如果我们直接把刚才那个整体的 JSON 数组 传进去,并试图用 List.class 去接,会发生什么?

Java 复制代码
import com.alibaba.fastjson.JSON;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;

public class DemoTrap {
    public static void main(String[] args) throws Exception {
        // 1. 读取我们的 test.json 文件(一个完整的 [] 数组字符串)
        String jsonArrayString = new String(Files.readAllBytes(Paths.get("test.json")));

        // 2. ❌ 致命错误示范:直接用 List.class 去接
        List<AgentResponse> list = JSON.parseObject(jsonArrayString, List.class);
        
        // 3. 表面上看没有报错,它甚至能打印出正确的大小!
        System.out.println("集合大小: " + list.size()); 
        
        // 4. 🚨 运行时爆炸:这一步必定抛出 ClassCastException 异常!
        AgentResponse firstResponse = list.get(0); 
    }
}

原因: 当我们在方法参数中传递 List.class 时,程序已经运行起来了。此时,JVM 遵循"类型擦除",把泛型便利贴无情撕掉。Fastjson 问 JVM:"这个 List 里面装什么?" JVM 答:"不知道,就个普通 List。"

既然不知道,Fastjson 只能用最基础的通用字典 JSONObject 来兜底。所以,list.get(0) 拿到的根本不是 AgentResponse,而是一个 JSONObject!强转必定报错。


2.5 TypeReference 终极破解

代码末尾的 {} 表示我们在现场创建了一个继承自 TypeReference 的匿名内部类

Java 规定,类的父类泛型信息会永久保留在字节码中。

Fastjson 拿到这个匿名类后,顺藤摸瓜查它的"族谱"(父类),就能找回完整的 <List<AgentResponse>> 基因信息!

Java 复制代码
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;

public class DemoSuccess {
    public static void main(String[] args) throws Exception {
        // 1. 读取我们一开始准备的 JSON 测试文件
        String jsonStr = new String(Files.readAllBytes(Paths.get("test.json")));

        // 2. ✅ 正确示范:使用带大括号 {} 的 TypeReference 锁死完整的泛型树
        List<AgentResponse> list = JSON.parseObject(
                jsonStr, 
                new TypeReference<List<AgentResponse>>() {} 
        );

        // 3. 验证成果
        for (AgentResponse agent : list) {
            System.out.println("----------");
            System.out.println("Agent 意图: " + agent.getIntent());
            System.out.println("第一个动作目标: " + agent.getActions().get(0).getTarget());
            System.out.println("执行置信度: " + agent.getConfidenceScores());
        }
    }
}

总结

JSON.parseObject() 本质是通过 反射 + 类型推导 将 JSON 字符串还原为 Java 对象。

在实际开发中,问题的核心并不在 API 本身,而在于 Java 的类型机制

  • 普通对象解析:依赖无参构造 + Setter,按字段映射即可完成
  • 类内部泛型:泛型信息保存在字节码中,Fastjson 可通过反射获取,解析无压力
  • 顶层泛型集合 :由于类型擦除 ,运行时丢失泛型信息,必须借助 TypeReference 补全类型

👉 一句话记住:

能解析 ≠ 写对了,关键在于"类型信息是否在运行时可获取"

相关推荐
KNeeg_1 小时前
黑马点评完整代码(RabbitMQ优化)+简历编写+面试重点 ⭐
java·redis·后端·spring·面试·职场和发展·黑马点评
铁皮哥2 小时前
【后端/Agent 开发】给你的项目配置一套 .claude/ 工作流:别再裸用 Claude Code 了!
java·windows·python·spring·github·maven·生活
乐之者v2 小时前
AI编程 -- codex添加代码,在intellij Idea中没有显示,如何处理?
java·ide·intellij-idea
2401_878820472 小时前
Sa-Token基础篇
java·spring boot·后端·sa-token
2301_816374332 小时前
Nginx下构建PC站点
java·运维·nginx
无所事事O_o3 小时前
JAVA应用不定时卡顿问题排查过程记录
java·优化
幸福巡礼3 小时前
【LangChain 1.2 实战(八)】Agent Middleware 实战 —— 动态路由、监控、安全与容错
java·安全·langchain
Byron__3 小时前
Java JVM核心知识点复习笔记
java·jvm·笔记
程序员小白条3 小时前
别盲目卷算法!2026 程序员\&大学生,最稳的 AI 技术进阶路线全梳理
java·网络·人工智能·网络协议·http·面试