YAML 和 JSON 的区别:语法对比、示例与选择建议
YAML 和 JSON 的区别,简言之:两者都是表示同类数据(对象、列表、字符串、数字、布尔值和 null)的文本格式。JSON 语法严格,使用花括号和引号,是 Web API 的默认格式。YAML 依靠缩进,支持注释、锚点和单个文件多文档,常用于配置文件。YAML 1.2 几乎是 JSON 的超集。
免费试用:YAML 转 JSON 转换器 免费使用,无需注册账号。
JSON(JavaScript Object Notation,由 RFC 8259 定义)是为程序之间交换数据而设计的。那么 YAML 是什么?它的名字是“YAML Ain't Markup Language”的缩写,设计初衷是方便人手动阅读和编辑数据。本文用两种格式展示同一份数据,对比二者的差异,并介绍各自的常见陷阱。想看看任意文件换成另一种格式是什么样子,可以把它粘贴到免费的 YAML 转 JSON 转换器或 JSON 转 YAML 转换器中。
同一份数据的 JSON 与 YAML 写法
下面是一个用 JSON 编写的小型应用配置:
{
"name": "web-app",
"version": "2.4.1",
"replicas": 3,
"debug": false,
"database": {
"host": "db.example.com",
"port": 5432
},
"features": ["login", "search"],
"maintainer": null
}
同样的数据用 YAML 表示,还多了一行 JSON 无法容纳的注释:
# Settings for the web app
name: web-app
version: 2.4.1
replicas: 3
debug: false
database:
host: db.example.com
port: 5432
features:
- login
- search
maintainer: null
两者解析后得到完全相同的对象。JSON 用 {}、[]、逗号和引号标记结构;YAML 则用缩进、key: value 键值对以及表示列表项的 -。YAML 中的大多数字符串根本不需要引号,所以它读起来更像设置文件,而不是代码。
YAML 和 JSON 的区别:核心差异一览
| JSON | YAML | |
|---|---|---|
| 结构 | 花括号、方括号和逗号 | 缩进(只能用空格) |
| 注释 | 不允许 | # comment |
| 字符串 | 始终使用双引号 | 通常不加引号;需要时使用 'single' 或 "double" 引号 |
| 数据类型 | 对象、数组、字符串、数字、布尔值、null | 同左,另有可选标签、部分 schema 中的时间戳以及自定义类型 |
| 复用 | 无 | 锚点 &、别名 * 和合并键 << |
| 单文件多文档 | 不支持(每个文件一个值) | 支持,用 --- 分隔 |
| 多行文本 | 只能用 \n 转义 |
块标量 | 和 > |
| 解析 | 语法小而严格;浏览器和许多标准库内置支持 | 语法更复杂;通常需要第三方库 |
| 典型用途 | REST API、package.json、日志、程序之间传递的数据 |
Kubernetes 清单、GitHub Actions 工作流、Docker Compose、Ansible |
有些格式两者都接受:OpenAPI 描述可以用任意一种编写,kubectl apply 既能读取 YAML 清单,也能读取 JSON 清单。一般来说,由程序读写的数据用 JSON 更合适;由人手动维护且需要注释的数据用 YAML 更合适。
YAML 是 JSON 的超集吗?
基本是。YAML 1.2 规范(2009 年)旨在让 YAML 成为 JSON 的严格超集,实际上 YAML 1.2 解析器几乎能读取任何 JSON 文档并返回相同的数据。但有两点需要注意:
- 重复键。 RFC 8259 只说对象的键应当唯一,而 YAML 要求必须唯一,因此严格的 YAML 解析器会拒绝
{"a": 1, "a": 2}。 - YAML 1.1 解析器。 许多广泛使用的库仍遵循较旧的 YAML 1.1 规则。例如,PyYAML 只把包含小数点的数字识别为浮点数,因此 JSON 数字
1e3会被解析成字符串"1e3"。
反过来则不成立:大多数 YAML 都不是有效的 JSON。
需要了解的 YAML 陷阱
挪威问题(Norway problem):NO 变成了 false
在 YAML 1.1 中,不加引号的 yes、no、on 和 off(无论小写、首字母大写还是全大写)都与 true、false 一样是布尔值。所以下面这个国家代码列表:
countries:
- GB
- NO
- SE
在 PyYAML 等 YAML 1.1 解析器中会被加载为 ["GB", false, "SE"]。同样的规则也会让 PyYAML 在加载 GitHub Actions 工作流文件时,把其中的 on: 键变成布尔值 True。YAML 1.2 的核心 schema 修正了这个问题:只有 true 和 false(也可写作 True 或 TRUE)是布尔值,因此 1.2 解析器会把 NO 保留为字符串。由于你很少能决定由哪个解析器读取你的文件,稳妥的习惯是给这类值加上引号:- "NO"。
不该被当作数字的数字
- 前导零。 YAML 1.1 把
0755读作八进制数,即 493。YAML 1.2 则把它读作十进制的 755,八进制要写成0o755。像01234这样的邮政编码,视解析器不同会变成 668 或 1234。请给它加上引号。 - 版本号。
python-version: 3.10是浮点数 3.1,而不是“3.10”。请写成"3.10"。 - 末尾的零。
1.0是浮点数,所以在 JavaScript 中转换为 JSON 时会变成1。
制表符与有意义的空白
YAML 规范禁止用制表符缩进,所以行首的制表符是语法错误,而不是风格问题。缩进本身也承载含义:把一个键向左移动两个空格,它就会归属到另一个父对象下,而文件依然完全有效,只是含义变了。
多行字符串:| 和 >
字面块(|)保留换行;折叠块(>)用空格把各行连接起来:
literal: |
Line one
Line two
folded: >
This long sentence is
folded into one line.
literal 的值为 "Line one\nLine two\n",folded 的值为 "This long sentence is folded into one line.\n"。两者都会保留末尾的一个换行符;写成 |- 或 >- 即可去掉它。
锚点、别名与合并键
YAML 可以只定义一次某个块,然后重复使用:
defaults: &defaults
adapter: postgres
port: 5432
production:
<<: *defaults
host: db.example.com
&defaults 为该块命名,*defaults 引用它,<< 则合并它的键,因此 production 最终包含 adapter、port 和 host。JSON 没有对应的机制:转换为 JSON 时,这些值会被复制到每个用到它们的地方。合并键来自 YAML 1.1,不属于 1.2 核心 schema,但大多数常用解析器仍然支持。
需要了解的 JSON 陷阱
JSON 很严格,大部分错误都源于以下三条规则:
- 不支持注释。
//和/* */都是语法错误。 - 不允许尾随逗号。
["a", "b",]是无效的。 - 只能用双引号。 键必须加引号,且不允许使用单引号。
因此,下面这个文件用 JSON.parse 解析会失败:
{
// port for local development
'port': 8080,
"tags": ["api", "v2",],
}
对于手写文件,有一些更宽松的变体:JSONC(“JSON with comments”,即带注释的 JSON)用于 VS Code 设置和 tsconfig.json,而 JSON5 还允许单引号、不加引号的键和尾随逗号。标准 JSON 解析器不接受其中任何一种。JSON 美化工具接受 JSON5 输入,会将其标记为“有效的 JSON5 — 已转换为严格 JSON”,并返回标准 JSON。
.yaml 与 .yml
YAML 和 YML 有什么区别?这其实不是格式问题:两个扩展名表示同一种格式,解析器并不在乎你用哪一个。2024 年注册了 application/yaml 媒体类型的 RFC 9512 将 .yaml 定为首选扩展名,同时指出 .yml 仍在使用。YAML 项目的 FAQ 也推荐 .yaml,而 Docker Compose 会先查找 compose.yaml,再查找 compose.yml。GitHub Actions 在 .github/workflows 中两者都接受。每个项目选定一种即可,不要让 config.yaml 和 config.yml 同时存在。
安全:安全地加载不可信的 YAML
完整的 YAML 加载器可以根据标签构建特定语言的对象。在 Python 中,yaml.load(data, Loader=yaml.UnsafeLoader) 可以创建任意 Python 对象,遇到精心构造的文件时甚至会执行代码。对于不是你自己编写的文件,务必使用 yaml.safe_load();它只会构建普通的 dict、list、字符串、数字、布尔值和 null。从 PyYAML 6.0 起,yaml.load() 在没有显式指定 Loader 时会拒绝运行。还要警惕嵌套很深的别名(“billion laughs”文件),它们会展开成巨大的结构。JSON 没有标签和别名,因此 JSON.parse 和 Python 的 json.loads 永远只返回普通数据。
在 YAML 与 JSON 之间转换
这两个转换器都完全在你的浏览器中运行,并使用开源的 JavaScript yaml 库。
YAML 转 JSON 转换器解析 YAML 1.2,展开锚点和别名,应用 << 合并键,并把包含多个 --- 文档的文件转换为一个 JSON 数组。你可以选择 2、3(默认)或 4 个空格、制表符或压缩输出,按字母顺序排序键,并将结果下载为 converted.json。语法错误(例如用制表符缩进)会连同行号和列号一起显示。注释会丢失,因为 JSON 无法保存注释。
JSON 转 YAML 转换器读取标准 JSON 和 JSON5,因此输入中的注释和尾随逗号可以被接受,但不会被保留。它输出两个空格缩进的 YAML,没有任何选项。输出遵循 YAML 1.2:"0755" 和 "true" 这样的字符串会加上引号,但 NO、yes 或 on 不加引号,因为它们在 1.2 中只是普通字符串。如果文件将由 PyYAML 等 YAML 1.1 工具读取,请自行给这些值加上引号。
在命令行中,Mike Farah 的 yq 可以双向转换,jq 则可以校验并格式化输出 JSON:
yq -o json config.yaml
yq -P -oy config.json
jq . config.json
何时使用 YAML,何时使用 JSON
JSON 与 YAML 之间如何选择,通常取决于由谁来编写文件:
- 使用 JSON 存储程序之间交换的数据:API 请求和响应、服务之间的消息、日志、浏览器存储,以及任何由代码生成的内容。它没有歧义,而且处处都受支持。
- 使用 YAML 编写由人编辑和审查的配置:部署清单、CI 流水线、Compose 文件。注释和更轻量的语法让 diff 更易读。
- 遵循工具的约定。 如果某个平台要求 YAML(GitHub Actions)或 JSON(
package.json),就直接使用该格式,而不是再做转换。
如果选择 YAML,就让它尽量“无聊”:两个空格缩进,凡是可能被读成布尔值或数字的内容都加上引号,并在 CI 中使用 yamllint 之类的 linter。
常见问题
YAML 比 JSON 更好吗?
总体上没有谁更好。JSON 更简单、更严格,因此是程序之间交换数据时更稳妥的选择。YAML 更便于人阅读和编辑,并且支持注释,这正是许多配置文件采用它的原因。
可以在 YAML 文件中使用 JSON 吗?
可以。YAML 的流式风格(flow style)使用与 JSON 相同的括号,所以 ports: [80, 443] 或 db: {"host": "localhost"} 在 YAML 文件中都能正常使用,而且 YAML 1.2 解析器几乎能接受任何完整的 JSON 文档。主要的例外是重复键。
JSON 可以写注释吗?
标准 JSON 不行:RFC 8259 没有注释语法,JSON.parse 遇到 // 或 /* */ 会报错。有些工具接受允许注释的 JSONC 或 JSON5,但在把文件交给严格解析器之前,你必须删除这些注释。
YAML 和 YML 有什么区别?
内容上没有区别:.yaml 和 .yml 是同一种格式的两个文件扩展名。RFC 9512 将 .yaml 定为首选扩展名,但 YAML 解析器并不关心扩展名。
为什么 YAML 会把 NO 或 on 变成 false 或 true?
因为 YAML 1.1 把 yes、no、on 和 off 视为布尔值。YAML 1.2 只把 true 和 false 视为布尔值,但包括 PyYAML 在内的许多解析器仍在使用 1.1 规则。给这类值加上引号,例如 country: "NO",所有解析器就都会把它们读作字符串。
免费试用:YAML 转 JSON 转换器 免费使用,无需注册账号。