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 文档并返回相同的数据。但有两点需要注意:

反过来则不成立:大多数 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 规范禁止用制表符缩进,所以行首的制表符是语法错误,而不是风格问题。缩进本身也承载含义:把一个键向左移动两个空格,它就会归属到另一个父对象下,而文件依然完全有效,只是含义变了。

多行字符串:| 和 >

字面块(|)保留换行;折叠块(>)用空格把各行连接起来:

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 很严格,大部分错误都源于以下三条规则:

因此,下面这个文件用 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 之间如何选择,通常取决于由谁来编写文件:

如果选择 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 转换器 免费使用,无需注册账号。