JSON 格式化、校验、JSON Schema 在实战中的差异

2026-04-13发布 8分钟阅读

摘要 (TL;DR)

上个月我合作的一支团队对 feature_flags.json 改了一行就发布了。JSON.parse 通过、CI 是绿的、上 staging 后所有结账分支全部回退到旧代码路径,原因是 flags.checkout_v2字符串 "true" 而不是布尔值 true。“检查 JSON”已经悄悄变成三件不同的事,他们只做了前两件。**Pretty-print(格式化)**只重排空白让人能读,不验证任何东西。语法校验确认字符串按 RFC 8259 能被解析为 JSON——括号匹配、引号正确、转义合法、数字字面量正确——这正是 JSON.parse 干的活。结构校验是另一步:解析后的值是不是你程序期望的形状?必填字段、类型正确、枚举值合法、字符串长度、数值范围。最后这一步正是 JSON Schema 设计的目的,也是大多数团队会跳过、直到出现上面这种生产事故才补上的那一层。可靠的管线在合适的层用上三件事:要看就格式化,要拒绝畸形输入就解析,在信任边界(API 入参、配置文件、跨服务消息)就用 Ajv 之类的 schema 校验。

背景与概念

JSON 由 RFC 8259(与等价的 ECMA-404)定义。语法刻意保持简洁:一个 JSON 文档要么是字符串、数字、truefalsenull、数组、对象之一。字符串用双引号包围,支持一小撮转义包括 \n\t\"\\\uXXXX(Unicode 码元)。数字遵循十进制语法——可选符号、可选小数、可选指数——但语法层不区分整数与浮点。对象是字符串键的无序成员集合,数组是有序值列表,字符串外的空白没有意义。

JSON 故意不包含的东西同样重要:没有注释没有尾随逗号没有单引号字符串、没有十六进制或二进制字面量。ASCII 范围之外的 Unicode 字符要么作为原生 UTF-8 字节出现,要么用 \u 转义。JSON5、HJSON 是放宽部分规则的不同格式,严格的 JSON 解析器不会接受它们。

文档解析成功后,“语法有效”和”是不是对的数据”是两回事。{"user": "x", "pass": "y"} 是完全合法的 JSON,但如果端点期望 {"username": "...", "password": "..."},从应用角度看就是错数据。要抓这一类错误就需要 schema——一份机器可读的”什么样的文档算合格”描述。JSON Schema(当前推荐元 schema 是 Draft 2020-12)填这个空缺。它支持必填字段、类型约束、enumconst、用正则的字符串模式、数值范围、数组的 items 与唯一性、对象的 propertiesadditionalProperties、用 allOfoneOfanyOf$ref 做组合。Ajv 8.12 把 schema 一次性编译成 JavaScript 校验函数,热路径上跑也不慢——我在一个 Node 20.11 服务里测过,对典型请求体的一次 Ajv 校验在几十微秒级别。

格式化是这三件里最平淡的:只插入空白——缩进和换行——不改变意义。JSON.stringify(obj, null, 2)jq .、IDE 格式化器都在做这事;浏览器开发者工具的网络面板也自动这么做。它对人类有用,对机器无关紧要。

对比与数据

维度格式化语法校验JSON Schema 校验
目的让数据可读确认文本能被解析为 JSON确认解析后的值符合预期形状
能发现无(仅空白)括号不匹配、引号错、非法转义、尾随逗号缺失字段、类型错、值越界、未知键
不能发现结构与语义问题期望的形状、业务规则schema 之外的语义、跨字段不变量
常用工具JSON.stringify(obj, null, 2)jq、IDE 格式化器JSON.parsejq -e、各语言解析器Ajv 8.x、python-jsonschema、OpenAPI 校验器
应用位置开发工具、日志每一处解析边界(隐式)API 请求/响应、配置加载、消息边界

三列不是”二选一”,而是顺序的层级。把 12MB 单行的 package-lock.json 扔进编辑器看,diff 工具拒绝比对,把它格式化成有缩进的形式并不验证任何东西——只是让形状可读。这区分很关键,因为下面两层(语法和 schema)经常被误以为是”我格式化了,没炸所以没事”。要读就格式化、要拦截畸形文本就解析、要抓错形状就用 schema。只做前两步是常见的漏洞。

实战场景

**场景 1 — 调试 API 响应。**第三方支付网关返回 600 行 JSON 但浏览器显示成一行。用浏览器开发者工具、本地格式化器或 curl ... | jq . 把它转成人能扫读的样子。这里没有发生校验,目的是”找哪里看着不对”,关键纪律是不要把这一步叫作”已校验”。

**场景 2 — 加载配置文件。**服务启动时读 config.json。严格 JSON 解析器会拦下尾随逗号这种语法错误并拒绝启动,这是正确行为。但正如开头那个事故,retries: "three" 替代 retries: 3 也能解析通过,只有当代码把字符串和数字比较时才会暴。我一直推荐的模式是在入口前几行就 Ajv.compile(schema)(config),失败就 process.exit(1)——五分钟的改动,在接下来两次有人手编文件时各帮我省下了一个 staging 事故。

场景 3 — 校验 OpenAPI 契约。团队发布 OpenAPI 3.1 文档,把端点、请求体、响应形状写在 components.schemas 下。契约测试用规范里的示例载荷对同一份 schema 校验——服务实现漂移时(比如本应返回字符串处开始返回整数),契约测试在客户端崩溃之前就发现错配。引擎是与孤立配置文件校验同一种 JSON Schema 校验器,差别仅在覆盖规模

常见误解

**“JSON.parse 就够校验了。“**它只校验语法,不校验形状。一个对象缺一半字段,解析器照样开心地返回。把 JSON.parse 当作拦截畸形文本的闸口,在数据穿越信任边界时再加一层 schema 校验。

**“JSON Schema 只在服务端用。“**在浏览器发请求前就校验能给用户即时反馈,也减少服务器负载。Ajv 之类的校验器在浏览器跑得很顺。服务端校验仍然必须做——永远别信客户端——但客户端校验改善 UX 的同时不会削弱安全模型。

“JSON5 就是带注释的 JSON。“JSON5 加了注释、无引号键、尾随逗号、十六进制数等。这让它作为人类编辑的配置格式更友好(最知名的使用者 tsconfig.json 用的其实是另一个非标准超集 JSONC),但严格遵循 RFC 8259 的消费者不会接受它。在消费者明确支持的地方用 JSON5/JSONC;要发到网络或经过任何你不掌控的解析器时,老老实实输出严格 JSON。

**“YAML 是带缩进的 JSON。“**每个有效 JSON 文档都是有效 YAML,但 YAML 加了 anchor/alias、标签类型、单文件多文档、块和折叠标量、缩进敏感解析等 JSON 没有的特性,引入了 JSON 不会有的 bug。最经典的是”挪威问题”:YAML 1.1 把不加引号的 NO 解释为布尔 false。“为了可读性”从 JSON 改用 YAML 是用一类问题换另一类问题。

决策清单

  1. **只是想看数据?**格式化即可,别叫它”已校验”。
  2. 数据要穿越信任边界吗(HTTP 请求、消息队列、配置文件)?解析之后再跑一层 JSON Schema 校验,不只是解析。
  3. **关心未知字段吗?**在 schema 里设 additionalProperties: false,并决定拒绝还是剥离多余字段的策略。
  4. **错误信息可执行吗?**把校验器配成(比如 Ajv 的 allErrors: true)一次返回所有违反项,用户能一次看到所有问题。
  5. **schema 离使用数据的代码近吗?**规范与实现的漂移在 schema 是双方真实源时更容易避免。
  6. **格式适合用 JSON 吗?**人要编辑且解析器你能选,JSON5/JSONC 或 TOML 更宽容。机器之间交换则坚持严格 JSON。

相关工具

Patrache Studio JSON 格式化器 在浏览器里运行,所以你粘贴的载荷不会离开机器——查看含个人信息的生产响应时很有用。JSON 载荷很少独行:如果里面嵌入了二进制内容,Base64 与 URL 编码:作用、陷阱、正确用法 解释为何体积膨胀以及何时该换种传输。如果含 ID,UUID v1、v4、v7 对比与数据库主键设计 讲服务端发哪一种版本会如何影响下游系统的索引、排序和缓存。

参考资料