JSON 语法更严格、解析更简单,适合 API 和机器交换;YAML 更适合人读,支持注释、锚点和多文档,常用于配置和 Kubernetes。需要转换时按消费方要求选择格式,转换后必须校验,因为注释、锚点和多文档在 JSON 中没有等价表示。
- JSON 和 YAML 能表达相似的数据,但语法、类型推断和安全模型不同。
- YAML 支持注释、锚点和多文档,JSON 不支持。
- YAML 的隐式类型会把未加引号的 yes、no、on、off 解析成布尔值。
- 两种格式都应该用真正的解析器,不要用正则或 split 解析。
- 不可信来源的 YAML 要当作代码处理,使用安全加载器并限制大小和深度。
JSON 和 YAML 经常同时出现:API 返回 JSON,部署文件用 YAML,JSON Schema 也可能用 YAML 保存。它们能表达相似的数据,但在语法、安全和工具链上并不能互换。
这篇文章比较真正影响使用的差异,并给出转换时的检查方法。
真正有用的问题不是“哪个更好”,而是“哪种失败模式你能接受”。JSON 出错通常直接、可预测;YAML 则可能通过隐式类型、缩进和锚点悄悄出错。选型时先看文件由谁编辑、由谁解析,再决定容忍哪种失败。
同一份数据的两种写法
{
"service": "api",
"replicas": 3,
"enabled": true,
"ports": [80, 443]
}
service: api
replicas: 3
enabled: true
ports:
- 80
- 443
数据相同,语法不同。YAML 依赖缩进和换行,JSON 依赖花括号、方括号、逗号和引号。
这两段代码能解析出同样的树,前提是每个值都没有歧义。把其中一行改成 version: 1.20,YAML 可能交给你数字 1.2,而 JSON 里的 "version": "1.20" 仍然是字符串。YAML 还支持流式写法,例如 {service: api, replicas: 3},看起来像 JSON,实际仍按 YAML 的引号和类型规则解析。不要因为一段文本长得像 JSON,就假设它按 JSON 解析。
影响使用的语法差异
| 特性 | JSON | YAML |
|---|---|---|
| 注释 | 不支持 | 支持 # |
| 键的引号 | 必须 | 可选 |
| 尾随逗号 | 不允许 | 不适用 |
| 缩进 | 不敏感 | 敏感 |
| 多文档 | 不支持 | 支持 --- |
| 锚点和别名 | 不支持 | 支持 & 和 * |
| 显式类型 | 仅 JSON 类型 | 标签和隐式推断 |
| 重复键 | 解析器行为不一致 | 规范要求唯一,解析器仍可能覆盖 |
| 制表符 | 可作空白 | 不能用于缩进 |
| 解析复杂度 | 较低 | 较高 |
JSON 语法小是优点:更容易解析、校验,也更不容易藏歧义。
重复键值得单独警告。RFC 8259 说键名应当唯一,却没有强制报错;于是一个解析器保留后值,另一个保留前值,第三个直接拒绝。YAML 规范要求键唯一,但很多解析器仍会静默覆盖。两种格式里的重复键都是数据丢失的隐患,只是要等换一个工具才暴露。应在文件进入生产前用 linter 检查。
制表符是另一个安静的区别。JSON 把制表符当普通空白;YAML 禁止用它缩进,但允许它出现在带引号的标量里。一个在制表符宽度为 4 的编辑器里看起来对齐的文件,可能在 CI 里直接解析失败,也可能在有人把制表符替换成空格后悄悄变成另一种结构。
YAML 有而 JSON 没有的能力
注释
replicas: 3 # 与 HPA 保持一致
JSON 没有注释。转成 JSON 后,解释会消失。如果注释重要,应该把 YAML 作为源文件,把 JSON 作为构建产物。
JSONC 和 JSON5 为 tsconfig.json、编辑器设置这类配置文件加了注释,但它们不是标准 JSON。发给只调用 JSON.parse 的接口会失败,报错往往指向注释而不是真正的问题。注释也不等于约束:如果注释写着“不要超过 10”,就应该再加一条 schema 约束或测试,因为下一个编辑文件的人未必会读它。
锚点和别名
defaults: &defaults
timeout: 30
retries: 3
production:
<<: *defaults
timeout: 60
锚点可以减少重复。JSON 没有合并键,转换器必须展开每个别名。生成的 JSON 更大,也不再表达共享结构。
有两个细节经常出问题。第一,<< 合并键属于 YAML 1.1 的类型约定,不在 YAML 1.2 核心 schema 里;有的解析器支持,有的要加开关,有的静默忽略。第二,别名在解析阶段就被展开,解析后的树没有“共享同一份数据”的概念。一个大量复用锚点的 10 MB YAML,转换后可能变成 100 MB JSON,恶意构造的别名炸弹还能指数级膨胀。要设置别名展开上限,也不要把锚点当成 schema 或模板引擎的替代品。
多文档
---
kind: Deployment
---
kind: Service
YAML 可以包含多个文档,JSON 文件只能有一个值。转换器必须决定输出数组、包装对象还是直接拒绝。
这几种选择并不等价。数组会丢掉源文件名、序号之类的每文档信息;包装对象改变了客户端预期的结构;只取第一个文档则会静默丢掉其余内容。Kubernetes 清单依赖 ---,只处理一个文档的转换器就是数据丢失 bug。还要注意块标量里的 ---:shell 脚本或证书里的一行横线不应开启新文档,解析器如果混淆两者,会把一个值从中间劈开。
YAML 的类型推断坑
YAML 会把未加引号的标量解析成类型。方便,也容易出错。
country: NO
enabled: yes
version: 1.20
在 YAML 1.1 中,NO 可能变成 false,yes 变成 true,1.20 变成数字 1.2。这就是著名的“挪威问题”。需要字符串时,加引号:
country: "NO"
version: "1.20"
YAML 1.2 收窄了布尔推断,但不同解析器的行为并不完全一致。加引号最可移植,也最明确。
坑远不止挪威问题。在 YAML 1.1 里,未加引号的 12:30 可能被解析成六十进制整数 750,012 变成八进制 10,1_000 变成 1000;.inf、.nan、~ 和空值会变成特殊数字或 null;有些解析器还会把 2026-11-09 转成日期对象,再序列化成 UTC 时间戳,日期可能因此偏移一天。GitHub Actions 里的 on: 键是著名案例:按 YAML 1.1 规则它会被当成布尔值 true,所以工具和文档都要特殊处理。
实用的规则比“布尔值要加引号”更宽:只要类型重要,就加引号。版本号、电话号码、邮编、账号、日期、时间和看起来像布尔的键,都应该加引号。用声明了 schema 版本的 linter,并在 CI 里固定解析器版本,因为升级一个 YAML 库就可能改变现有文件的解析结果。
JSON 的限制
JSON 刻意保持很小:没有注释、没有日期类型、没有二进制类型、没有引用。日期是字符串,二进制通常用 Base64,共享结构需要重复写。
这些限制让 JSON 更可预测。解析器不需要猜 NO 是字符串还是布尔值。
有两个 JSON 限制需要显式处理。多数解析器用 IEEE 754 双精度浮点数表示数字,超过 2^53 的整数会丢精度;一个 19 位的数据库 ID 在 JavaScript 里解析后可能变成另一个数。这类 ID 应该用字符串传输,或者换支持大整数的解析器。另外,JSON 标准没有流式分隔符,JSON Lines(每行一个对象)只是约定,但它是日志和大批量导出的实用选择,因为消费方可以一次处理一条记录。
JSON 的严格性也是安全优势。JSON.parse 不会构造对象、调用函数或解析引用;危险的是应用代码拿到结果之后的行为,例如把不可信的键合并进对象,触发原型污染。
转换时的坑
JSON 转 YAML 时:
- 现代库通常保留键顺序,但不要依赖顺序表达语义。
- 看起来像数字或布尔值的字符串,在 YAML 输出中应该加引号。
- 部分库会把大整数输出成科学计数法,需要检查。
- Unicode 通常保留,但转义形式可能变化。
YAML 转 JSON 时:
- 注释会丢失。
- 锚点和别名会被展开。
- 多文档必须合并或显式拒绝。
- 自定义标签可能不被支持。
- 隐式类型可能因转换器的 YAML 规则不同而变化。
值得写进测试的具体故障:YAML 的 enabled: yes 转成 JSON 后是 true,不是 "yes";未加引号的 version: 1.20 变成 1.2,版本比较依赖的末尾零丢了;块标量 | 保留结尾换行,> 会折叠换行,校验和或 PEM 内容可能因此变化;!!binary 在不同转换器里可能变成 Base64 字符串、字节数组或直接报错;被解析成本地日期的值转成 UTC 时间戳后,东半球用户的日历日期可能差一天。
转换后一定要校验。JSON 用 JSON 格式化与校验工具,YAML 用 YAML 格式化与校验工具。一次性转换可以用 JSON ↔ YAML 转换工具,Kubernetes 清单可以用 Kubernetes YAML 检查工具 复核后再应用。
什么时候用 JSON
- API 请求和响应正文。
- 服务之间的数据交换。
- 浏览器存储和配置。
- 需要小语法、严格解析和快速处理的场景。
- 对歧义零容忍的公开契约。
什么时候用 YAML
- 人工维护的配置文件。
- CI/CD 流水线。
- Kubernetes 清单和 Helm values。
- 注释和锚点能显著提高可读性的文件。
- 需要人评审和维护的场景。
YAML 是配置语言,JSON 是数据交换格式。用 YAML 传 API 数据可以工作,但会增加解析复杂度和类型意外,收益很小。
这个建议反过来也成立。用 JSON 维护人工配置同样不舒服:不能写注释、键必须加引号、没有锚点,报错还只给字节偏移而不给行号。文件主要由人每周修改时,YAML 是更好的默认值;主要由程序读写时,JSON 更合适。OpenAPI 是常见的折中:规范常用 YAML 编写、以 JSON 发布,YAML 是源文件,JSON 是生成产物。
安全:解析不可信 YAML
在很多语言里,YAML 不只是数据。标签可以构造对象,锚点可以指数级展开。恶意 YAML 可能造成拒绝服务,或实例化预期外的类。
规则:
- 使用只产生 map、list 和标量的安全加载器。
- 限制文档大小、嵌套深度和别名展开次数。
- 不要把不可信 YAML 反序列化成应用对象。
- 不可信网络输入优先使用 JSON。
JSON 解析器也有资源限制问题,但攻击面更小,因为语法不包含对象构造。
不安全加载器的问题与语言有关,模式却通用:如果加载器允许标签实例化任意类,文档就能决定解析时执行哪段应用代码;来自用户或第三方集成的 YAML 绝不能进入这种加载器。即使使用安全加载器,也要限制资源,因为锚点和深层嵌套可以耗尽内存或调用栈。把解析当作不可信输入处理:限制文档大小、嵌套深度和别名数量,并把解析放在有内存上限的进程里。
JSON 也不是自动安全。解析阶段不会执行代码,但深层嵌套仍可能压爆调用栈,超大文档仍可能耗尽内存。两种格式都要做大小和深度限制;区别在于 YAML 还要防对象构造。
实用转换流程
- 明确事实来源:配置用 YAML,API 数据用 JSON。
- 单向转换,把结果作为构建产物。
- 用目标格式的校验器验证输出。
- 和上一版本做 diff。
- 注释和锚点保留在源文件,不要求在生成文件中保留。
- 把转换放进 CI,保证可重复。
JSON 转 YAML 工具 可以处理常见情况。Kubernetes 清单在应用前,用 Kubernetes YAML 检查工具 复核一次。
在 CI 里让转换可重复:固定转换器版本,提交生成文件,重新生成出现 diff 时让构建失败。生成产物除了能解析,还要按 schema 校验;保留几份覆盖锚点、多文档和带引号标量的 golden file。不要手改生成文件,下一次构建会覆盖它,两个事实来源很快会漂移。
一句话结论
| 场景 | 更合适的选择 | 原因 |
|---|---|---|
| 公开 API 正文 | JSON | 语法严格,客户端支持广 |
| 人工维护的部署配置 | YAML | 注释、锚点、diff 可读 |
| 程序生成的配置 | 都可以 | JSON 生成更简单、更安全 |
| 日志和大批量导出 | JSON Lines | 一行一条,可流式处理 |
| 不可信网络输入 | JSON | 没有基于标签的对象构造 |
| 需要评审注释的文件 | YAML | 注释能留在评审里 |
机器交换数据、强调严格性时用 JSON;人类维护配置、需要注释时用 YAML。转换要有明确方向,两边都校验,并把看起来像布尔值或数字的字符串加上引号。
这个建议也有失效的时候。只使用 Kubernetes 的团队,不该为了纯粹而引入 JSON 清单;前端团队已经在用 JSONC 写配置,也不必为了注释迁到 YAML。更常见的问题是双向同步:选一个事实来源,只往一个方向转换,并禁止手工编辑生成产物。
常见问题
YAML 是 JSON 的超集吗?
YAML 1.2 在设计上兼容 JSON,很多 JSON 文档也是合法 YAML。但反过来不成立:YAML 的注释、锚点、标签和多文档在 JSON 中没有直接对应。
为什么 YAML 会把 yes 解析成 true?
YAML 1.1 会把未加引号的 yes、no、on、off 解析成布尔值。YAML 1.2 收窄了规则,但很多解析器仍按 1.1 行为处理。需要字符串时应该加引号。
JSON 可以写注释吗?
标准 JSON 不支持注释。JSON5 和 JSONC 为配置场景增加了注释,但它们不是标准 JSON,不应该发送给只接受标准 JSON 的接口。
JSON 和 YAML 哪个解析更快?
JSON 通常更快,因为语法更小、解析器更简单。YAML 还要处理缩进、多文档和隐式类型,解析成本更高。高频 API 应优先使用 JSON。
解析不可信 YAML 安全吗?
使用会构造任意对象的加载器时不安全。YAML 标签在某些语言中可以实例化应用类,锚点还可能造成指数级展开。应该使用只产生 map、list 和标量的安全加载器,并限制文档大小和嵌套深度。