YAML 和 JSON 的区别——先说结论
一句话:API 和机器之间的数据交换用 JSON,人手写的配置文件用 YAML。两种格式描述的是同一套数据模型——对象、数组、字符串、数字、布尔值——但 JSON 严格且冗长,YAML 基于缩进、支持注释、读起来更清爽。
| JSON | YAML | |
|---|---|---|
| 语法 | 花括号和方括号 | 缩进(空格) |
| 注释 | 不支持 | 支持(#) |
| 主要用途 | REST API、数据交换、存储 | 配置文件(Kubernetes、GitHub Actions、Docker Compose) |
| 可读性 | 面向机器 | 面向人 |
| 严格程度 | 严格 | 宽松(隐式类型推断) |
核心要点
- JSON 严格、解析速度快、适合机器处理;YAML 基于缩进、人读起来舒服、能写注释
- REST API、日志流、高吞吐场景用 JSON;Kubernetes、GitHub Actions、Ansible 这类需要人编辑的配置文件用 YAML
- YAML 原生支持注释、锚点(引用)、多行字符串、日期类型,JSON 都没有
- 喂给 LLM 提示词时,YAML 比带缩进的 JSON 省约 20% 令牌(本站实测 -22%)。但如果用压缩 JSON(去掉所有空白),结果反转,JSON 反而更省。JSON 解析更快,面对不可信输入也更安全
这篇文章会依次讲清楚语法差异、同一份数据两种写法的对比、实际场景中的选型标准,以及转换时容易踩的坑。2026 年以来越来越多人关注"喂给 LLM 提示词用哪种格式更省令牌",这里也一并覆盖。
手头有一份配置文件想看两种格式的差异,可以把 YAML 粘贴到 YAML to JSON 转换工具,转出来的 JSON 再丢回去对比,一分钟内就能看出同一份数据在两种格式下的变化。所有处理都在浏览器里完成,数据不会上传到任何服务器。
实际场景中的选型
在看语法之前,先说实际工作中"选哪个"的常见判断:
- Kubernetes 清单、Helm Chart——YAML
- Docker Compose 文件——YAML
- GitHub Actions、GitLab CI、CircleCI 流水线配置——YAML
- Ansible Playbook——YAML
- REST API 的请求体和响应体——JSON
package.json、composer.json、浏览器与服务端通信——JSON- 日志记录、事件流——JSON Lines(JSONL)
- OpenAPI / Swagger 规范——YAML 或 JSON(人写用 YAML,工具生成 JSON 的居多)
规律很清楚:人读人写的文件用 YAML,程序生成和消费的数据用 JSON。拿不准的时候就记住这一条。
语法对比:同一份数据,两种写法
拿一个简单的 Web 服务器配置,分别用两种格式写一遍。
YAML:
# Web 服务器配置
server:
host: localhost
port: 8080
debug: true
allowed_origins:
- https://example.com
- https://staging.example.com
headers:
X-Frame-Options: DENY
Strict-Transport-Security: "max-age=31536000"
JSON:
{
"server": {
"host": "localhost",
"port": 8080,
"debug": true,
"allowed_origins": [
"https://example.com",
"https://staging.example.com"
],
"headers": {
"X-Frame-Options": "DENY",
"Strict-Transport-Security": "max-age=31536000"
}
}
}
值得注意的有两点。第一,YAML 去掉了大部分花括号、引号和逗号,代码更短。数一下上面两段:YAML 232 字符,JSON 291 字符,差约 20%(去掉 JSON 写不了的注释行后差距约 25%)。第二,开头那行注释 # Web 服务器配置只有 YAML 能保留,转成 JSON 后这条信息就没了。
数据类型:YAML 有而 JSON 没有的
两种格式都支持字符串、数字、布尔值、null、对象、数组。YAML 额外支持几种类型,写配置文件时特别方便。
日期和时间戳
YAML 解析器把 ISO 8601 格式的日期当作一等公民来识别。
release_date: 2026-04-01
created_at: 2026-04-01T09:30:00Z
JSON 没有日期类型,只能当字符串存。接收端得自己解析。
{
"release_date": "2026-04-01",
"created_at": "2026-04-01T09:30:00Z"
}
多行字符串
YAML 有两种写法处理长文本。字面块(|)保留换行,折叠标量(>)把换行替换成空格。
description: |
这个服务接收 GitHub 的 Webhook,
然后转发到内部队列。
summary: >
所有流量通过 Cloudflare 代理,
源站限速 100 rps。
JSON 要在同一行里用 \n 转义:
{
"description": "这个服务接收 GitHub 的 Webhook,\n然后转发到内部队列。\n",
"summary": "所有流量通过 Cloudflare 代理,源站限速 100 rps。"
}
隐式类型推断
YAML 会尝试从无引号的值推断类型。根据规范版本不同,yes、no、on、off 可能被解析为布尔值,看起来像数字的值会变成数字。人写的时候方便,但也是隐蔽 bug 的来源。最出名的例子就是"挪威问题"——country: NO 被解析成布尔值 false。JSON 里所有字符串都必须加引号,类型都是显式的,不会有这种歧义。
注释:YAML 在配置文件上的天然优势
YAML 注释从 # 开始,到行尾结束。用来写"为什么设这个值"、"有什么约束"非常实用。
retries: 3 # 再大就超过上游超时了
JSON 标准(RFC 8259)没有注释语法。所以出现了 "_comment": "..." 这种假字段、或者用非标准的 JSONC / JSON5 来绕过限制的做法——都是为了弥补这个缺陷。JSON 注释的替代方案详见 JSON 可以注释吗。
锚点和别名:YAML 的复用机制
YAML 允许定义一次值,在多处引用。用 & 设锚点,用 * 做别名引用,<<: 合并键可以把公共块拉进来。
defaults: &defaults
adapter: postgres
host: db.internal
pool: 5
development:
<<: *defaults
database: myapp_dev
production:
<<: *defaults
database: myapp_prod
pool: 20
转成 JSON 后,锚点被内联展开,development 和 production 各拿到一份完整的默认值副本。数据没错,但"共享同一组默认值"这个原始意图在 JSON 结构里看不出来了。
解析性能和文件大小
机器间传数据,JSON 更快。主流语言运行时都内置了高度优化的 JSON 解析器(JSON.parse、json.loads、encoding/json)。YAML 语法复杂、规则多,解析速度慢,大部分 YAML 库内部也是构建一棵 JSON 等价的树。
文件大小取决于"跟哪种 JSON 比"。用本站做令牌测试的同一份 32 行样本(scripts/benchmarks/yaml-vs-json-tokens/)实测:YAML 667 字节,带缩进的 JSON 857 字节,YAML 小约 22%。但去掉所有换行和空格的压缩 JSON 是 686 字节,跟 YAML 几乎一样。传输前走一遍 gzip 压缩的话,这点差距也基本消失。YAML 真正的成本不在字节数,而在解析时的 CPU 开销和隐式类型推断带来的意外行为。
什么时候该选 YAML
- 文件是人读、人手动编辑的
- 是配置文件,不是数据载荷
- 需要注释来解释配置意图
- 工具链已经默认 YAML(Kubernetes、Docker Compose、Ansible、CI)
- 想用锚点和别名减少重复
手头有一份 JSON 配置,想转成 YAML 提高可读性,可以用 JSON to YAML 转换工具,嵌套结构和缩进在浏览器里直接看。更多关于 JSON 转 YAML 的方法,参见 JSON 转 YAML 指南。
什么时候不该用 YAML
看起来该用 YAML 的场景,实际用反而出问题:
- 程序自动生成的配置文件——隐式类型推断会把
version: 2.0变成数字2,数据悄悄被改 - 实时高性能数据流——解析开销在高吞吐下不可忽略
- 安全敏感的输入——YAML 的
!!python/object标签在 unsafe loader 下可以执行任意代码。务必用yaml.safe_load或等效的安全加载器 - 短而扁平的数据——缩进省不了多少,JSON 反而更直观
什么时候该选 JSON
- 数据的生产和消费都是程序
- 需要跨语言互操作,不想有隐式类型转换
- 数据通过 API 在网络上传输
- 要求解析结果严格一致、没有歧义
- 性能敏感,需要解析快且可预测
人写的 YAML 要交给只认 JSON 的程序处理,这种情况日常很常见。用 YAML to JSON 转换工具可以在浏览器里转,语法错误会带行号提示,方便在部署前发现被类型推断改掉的值。
决策矩阵:YAML 还是 JSON,一表看清
把上面的选型标准浓缩成一张表。在左列找到你的场景,看对应行的结论。
| 判断标准 | YAML | JSON |
|---|---|---|
| 人手动编辑 | 合适——符号少、能写注释 | 不合适——符号多、不能写注释 |
| 机器间数据交换 | 不合适——解析慢、标量类型模糊 | 合适——有快速的原生解析器 |
| 需要注释 | 原生支持(#) | 不支持(JSONC/JSON5 是非标准替代) |
| 文件内复用 | 锚点、别名、合并键 | 没有——只能重复或改结构 |
| 类型行为 | 隐式推断,因解析器而异(见下方实测表) | 所有环境严格一致 |
| 解析器可用性 | 多数语言需要外部库 | 所有主流运行时内置 |
| 单文件多文档 | 支持(--- 分隔) | 不支持——一个文件一个值 |
| 多行字符串 | 专用语法(| 和 >) | 一行内用 \n 转义 |
| LLM 令牌成本 | 比带缩进的 JSON 少 | 压缩 JSON 最少 |
| 主要使用场景 | Kubernetes、CI、Docker Compose、配置文件 | API、日志、数据管道、存储 |
YAML 转 JSON 时的常见陷阱
转换大体上是有损的,但有几个边界情况容易翻车。
- 注释丢失——JSON 没有注释语法,所有
#开头的行全部被丢掉 - 锚点展开——共享块被复制到每个引用位置,数据重复
- 日期变字符串——ISO 日期丢失类型信息,变成普通字符串
- 类型推断的意外结果——
version: 2.0这种 YAML 值在 JSON 里可能变成数字2 - 多文档处理——
---分隔的多个 YAML 文档没法塞进一个 JSON 文件,要么选一个,要么用外层数组包起来 - 重复键——YAML 的行为因实现而异,JSON 里只保留最后一个值
- 标签和自定义类型——YAML 的
!!timestamp或自定义标签在 JSON 里没有对应类型
实测:同一份 YAML,4 个解析器,4 种结果
"类型推断的意外结果"听起来抽象,直到你看到同一行 YAML 在不同库面前变成不同的值。把一组有歧义的标量值分别喂给 4 个主流解析器(js-yaml 4.1.1、yaml/eemeli 2.8.3、PyYAML 6.0.3 safe_load、ruamel.yaml 0.19.1),复现脚本和原始数据在 scripts/benchmarks/yaml-parser-differences/,2026-07-10 在 Apple M5 Pro 上测的。转出来的 JSON 里实际拿到的是:
| 无引号 YAML 值 | js-yaml 4.1.1 | yaml (eemeli) 2.8.3 | PyYAML 6.0.3 | ruamel.yaml 0.19.1 |
|---|---|---|---|---|
NO(挪威问题) | "NO" | "NO" | false | "NO" |
on | "on" | "on" | true | "on" |
010(旧式八进制) | 10 | 10 | 8 | 10 |
0o10(YAML 1.2 八进制) | 8 | 8 | "0o10" | 8 |
1:30(六十进制) | "1:30" | "1:30" | 90 | "1:30" |
2026-07-10(裸日期) | Date 对象 | "2026-07-10" | date 对象 | date 对象 |
a: 1 + a: 2(重复键) | 报错 | 报错 | 后者优先(2) | 报错 |
<<: *base(合并键) | 正常合并 | 保留字面 "<<" 键 | 正常合并 | 正常合并 |
从转换角度看,有三个发现很重要。第一,挪威问题不是"YAML 的锅"——4 个解析器里只有 PyYAML(YAML 1.1 系加载器)把 NO 变成 false,另外 3 个遵循 YAML 1.2 的解析器保留字符串 "NO"。第二,裸日期分成三种情况:eemeli 的 yaml 返回纯字符串,js-yaml、PyYAML、ruamel.yaml 生成日期对象,后续 JSON 序列化时你得另外处理。第三,合并键 <<: 在 eemeli yaml 默认配置下行为不同:不合并共享块,而是把字面 "<<" 键留在 JSON 输出里——这种数据损坏往往要到下游服务报错才发现。
如果你的流水线里用的解析器不可控,转之前把有歧义的值("NO"、"1:30"、"2026-07-10")加上引号最保险。拿一份有代表性的文件粘贴到 YAML to JSON 转换工具,部署前就能在浏览器里看到每个值会变成什么。YAML 转 JSON 的完整陷阱清单见 YAML 转 JSON 指南。
用 FormatArc 转换 YAML 和 JSON
FormatArc 所有转换在浏览器内完成。不用上传文件,不用注册账号,数据不出你的机器。


- YAML to JSON——粘贴 YAML 得到 JSON,语法错误时显示行号
- JSON to YAML——反向转换,输出保持嵌套结构的整洁 YAML
- JSON 格式化——格式化转换后的 JSON,验证语法错误
脚本或自动化流水线里处理的话,yq在新标签页中打开 或 Python 的 yaml.safe_load 是常规选择。Python 一行命令:
python3 -c "import yaml,json,sys; print(json.dumps(yaml.safe_load(sys.stdin),indent=2))" < config.yaml
常见问题
YAML 是 JSON 的超集吗?
YAML 1.2 规范起,合法的 JSON 也是合法的 YAML。也就是说 YAML 解析器能直接读 JSON 文件。反过来不行——JSON 解析器读不了 YAML。不过很多 YAML 库默认还是用旧的 YAML 1.1 规范,兼容性不是 100%。
Kubernetes 和 Docker Compose 为什么用 YAML 而不是 JSON?
因为配置文件是人读人写的。基于缩进、支持注释的 YAML 在维护大型基础设施配置时更友好。需要输出状态的时候(比如 kubectl get ... -o json),这些工具都会另外提供 JSON 模式给机器用。
YAML 解析比 JSON 快吗?
不是。JSON 解析在所有主流语言里都快得多。JSON 解析器高度优化,YAML 语法丰富且有歧义,解析自然慢。性能敏感的数据传输场景,JSON 是更好的选择。
JSON 转 YAML 会丢数据吗?
通常不会。JSON 功能更简单,JSON 转 YAML 几乎无损。你没法加上 JSON 原本没有的注释,但数据本身可以完整往返。
为什么 YAML 里写 country: NO 会变成 false?
这就是"挪威问题"。旧版 YAML 解析器(YAML 1.1 规范)把无引号的 NO 解析为布尔值 false。写成 country: "NO" 加引号就解决了。YAML 1.2 规范已经去掉了这个行为,但很多工具默认还是 1.1。
喂给 LLM 提示词,YAML 和 JSON 哪个更省令牌?
取决于你拿哪种 JSON 来比。本站用 32 行 Kubernetes 风格样本(scripts/benchmarks/yaml-vs-json-tokens/)配合 OpenAI 的 tiktoken 库实测。
跟带缩进的 JSON(json.dumps(data, indent=2))比,YAML 在 cl100k_base 和 o200k_base 两种编码下都省约 22% 令牌(YAML 218 个 token vs 带缩进 JSON 281 个)。
但跟压缩 JSON(json.dumps(data, separators=(",", ":")))比,结果反转,JSON 反而省约 18%~21%(压缩 JSON 180 个 token)。
嵌套很深的 JSON,每个 {、}、[、]、" 都单独消耗一个 token,而 YAML 用缩进表达层级、不写闭合括号。JSON 去掉空白后差距就抹平了。
实际选型的判断标准:
- 能接受压缩 JSON 的场景,JSON 最省
- 人也要读的配置、或者包含多行字符串的数据,YAML 可读性好且总体更省
- 部分模型对严格 JSON 输出更稳定,所以 2026 年常见的模式是"输入用 YAML、输出要求 JSON"的不对称用法
YAML 的注释转成 JSON 后还在吗?
不在了。注释是绑定在源文件行号上的信息,一旦解析成结构化数据对象就丢掉了。转成 JSON 后无法恢复。需要保留注释的话,把原始 YAML 文件留着。
总结
- YAML 和 JSON 描述同一套数据模型,区别在于"谁在读"
- 人手编辑的配置文件用 YAML,机器间的数据交换用 JSON
- 转换时注意隐式类型推断、锚点展开、注释丢失
- FormatArc 在浏览器里完成双向转换,语法错误带行号提示