JSON 格式化、校验、JSON Schema 在实战中的差异
摘要 (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 文档要么是字符串、数字、true、false、null、数组、对象之一。字符串用双引号包围,支持一小撮转义包括 \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)填这个空缺。它支持必填字段、类型约束、enum 与 const、用正则的字符串模式、数值范围、数组的 items 与唯一性、对象的 properties 与 additionalProperties、用 allOf、oneOf、anyOf、$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.parse、jq -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 是用一类问题换另一类问题。
决策清单
- **只是想看数据?**格式化即可,别叫它”已校验”。
- 数据要穿越信任边界吗(HTTP 请求、消息队列、配置文件)?解析之后再跑一层 JSON Schema 校验,不只是解析。
- **关心未知字段吗?**在 schema 里设
additionalProperties: false,并决定拒绝还是剥离多余字段的策略。 - **错误信息可执行吗?**把校验器配成(比如 Ajv 的
allErrors: true)一次返回所有违反项,用户能一次看到所有问题。 - **schema 离使用数据的代码近吗?**规范与实现的漂移在 schema 是双方真实源时更容易避免。
- **格式适合用 JSON 吗?**人要编辑且解析器你能选,JSON5/JSONC 或 TOML 更宽容。机器之间交换则坚持严格 JSON。
相关工具
Patrache Studio JSON 格式化器 在浏览器里运行,所以你粘贴的载荷不会离开机器——查看含个人信息的生产响应时很有用。JSON 载荷很少独行:如果里面嵌入了二进制内容,Base64 与 URL 编码:作用、陷阱、正确用法 解释为何体积膨胀以及何时该换种传输。如果含 ID,UUID v1、v4、v7 对比与数据库主键设计 讲服务端发哪一种版本会如何影响下游系统的索引、排序和缓存。
参考资料
- IETF RFC 8259, “The JavaScript Object Notation (JSON) Data Interchange Format” — https://datatracker.ietf.org/doc/html/rfc8259
- JSON Schema 规范(Draft 2020-12)— https://json-schema.org/specification
- Ajv — Another JSON Schema Validator — https://ajv.js.org/
- MDN, “Working with JSON” — https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/JSON