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 补全类型

👉 一句话记住:

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

相关推荐
IT枫斗者枫哥6 分钟前
Java 文件导出:写完了,为什么还不能标记成功?
java
陈皮波比茶12 分钟前
java笔记-typora快捷键
java
蚂蚁雅嘿21 分钟前
maven标签日常总结
java·maven
洋不写bug32 分钟前
二叉树(二) 常见基础操作解析|结点数、树高、查找结点、判断完全二叉树
java·开发语言·数据结构·完全二叉树·二叉树结点数·树高·查找结点
菠萝加点糖43 分钟前
Maven 坐标系统说明
java·maven
hm宋1 小时前
Canal 升级 jar 包操作文档(以升级 FastJSON 为例)
java·canal·jar
后台模板学习1 小时前
学习的心态高频面试题
java·数据库·学习
用户3126874877201 小时前
线程池到底怎么管理线程的?从 ThreadPoolExecutor 到拒绝策略全链路拆解
java
cpolar技术支持2 小时前
Kafka Streams 窗口统计怎么验收:本地跑订单流聚合,用 cpolar 给同事看只读结果页
java·docker·kafka·cpolar·kafka streams
爱读源码的大都督2 小时前
DeepSeek面试官问:生产RAG系统回答不准确,该如何定位和优化?这样回答,能让面试官当场给你Offer!
java·后端·python