D
开发工具箱

JSON 与 YAML 互转:配置文件格式的选择之道

数据格式 2026年6月15日 约 1 分钟阅读

什么是 YAML?

YAML 的全称是 “YAML Ain’t Markup Language”——对,你没看错,这个递归缩写本身就是一个程序员的幽默。跟 JSON 一样,YAML 是一种数据序列化格式。但它的设计哲学和 JSON 截然相反:JSON 为机器解析而生,YAML 为人眼阅读而造

来看同一份数据,JSON 长这样:

{
  "server": {
    "host": "192.168.1.1",
    "port": 8080,
    "tls": true,
    "routes": ["/api", "/admin", "/health"]
  }
}

转成 YAML 之后:

server:
  host: "192.168.1.1"
  port: 8080
  tls: true
  routes:
    - /api
    - /admin
    - /health

花括号、引号、逗号全消失了,层级关系靠缩进来表达。说实话,我第一次在 Docker Compose 里看到这种省略号、靠空格对齐的写法时,觉得这东西简直是在开倒车——直到某天需要手写一份 Kubernetes 的多容器部署配置,JSON 版本写到我怀疑人生,才懂了 YAML 的好。

YAML 的工作原理

YAML 的解析核心只有一句话:空白即语法,缩进即层级

缩进就是一切

YAML 用缩进表示嵌套关系,和 Python 一样。规则简单到只有两条:同级缩进必须相同,子级比父级多缩进一层(通常是 2 或 4 个空格)。没了。

但你可能会想:那我混用空格和 Tab 会怎样?答案是解析器直接报错,不会给你任何侥幸。这是我当年踩的第一个坑——编辑器自动把空格转成了 Tab,一个 20 行的配置文件我排错了半小时。

标量类型自动推断

YAML 不需要你声明类型。解析器看到 8080 就知道是整数,看到 true 就知道是布尔值,看到 "192.168.1.1" 也不会把它当成什么奇怪的东西。这种”不写类型标签,靠上下文推断”的能力,叫标量自动解析

有意思的是,YAML 支持 JSON 不支持的多行字符串

description: |
  这是第一行。
  这是第二行。
  空行和缩进都会被保留。

竖线 | 表示”保留换行”,右尖括号 > 表示”把换行折叠成空格”。写 CI/CD 配置里嵌脚本的时候,这两个操作符简直是救星。

YAML 转 JSON 的过程中发生了什么

把 YAML 转成 JSON 时,解析器做的事情其实很直接:

  1. 按缩进深度构建一棵树
  2. 缩进关系映射为 JSON 的嵌套对象/数组
  3. 标量值根据 YAML 的标签规则映射为 JSON 的 string / number / boolean / null
  4. 锚点和别名展开(后面会讲到)
  5. 输出纯净的 JSON

反之,JSON 转 YAML 是从严格的显式结构推导出最简的缩进表示——这个方向没有歧义,因为 JSON 的语法比 YAML 严格得多。

核心特性

特性说明
缩进敏感用空格表达层级,不能混用 Tab
无分隔符没有 {}[], 和引号(绝大多数情况)
注释支持原生支持 # 注释,JSON 完全不支持
类型自动推断整数、浮点、布尔、null 自动识别
锚点与别名& 定义锚点、* 引用,减少重复
多文档一个 YAML 文件可以包含多个文档,用 --- 分隔

注释这件事,单独拎出来说。JSON 的设计者 Douglas Crockford 是故意不加注释支持的——他认为注释会被滥用来存放解析指令,破坏数据格式的纯粹性。但从运维的角度看,一个没有注释的配置文件简直就是灾难。你打开一个 500 行的 JSON 配置,想知道某个字段是干什么的——对不起,只能去翻文档。YAML 的 # 注释解决了这个切肤之痛。

实际应用场景

1. Kubernetes 资源配置

Kubernetes 社区选 YAML 几乎是一个必然选择。一个典型的 Pod 定义里塞满了元数据、标签选择器、容器声明、挂载卷、环境变量——用 JSON 写的话,花括号能堆到七八层深,人眼根本数不过来谁嵌套了谁。YAML 的缩进结构天然匹配了”声明式资源描述”这个范式:

apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod
  labels:
    app: nginx
spec:
  containers:
    - name: nginx
      image: nginx:1.21
      ports:
        - containerPort: 80

同样的内容用 JSON 大概要 40 行,YAML 15 行就够了。

2. Docker Compose

多容器编排是 YAML 的天生主场。docker-compose.yml 里的 service、volume、network 三层嵌套用 YAML 表达出来一目了然,写起来和在脑子里画架构图的体验几乎一样。

3. Ansible Playbook

Ansible 选 YAML 有一个更深的原因:运维人员不是程序员。让一个运维工程师去手写 JSON 的自动化脚本——那个心理门槛和让一个网管去学编程差不多。YAML 的写法接近自然语言清单,学习曲线平缓得多。说白了,写 Ansible playbook 的感觉更像在填 Excel 表,而不是在写代码。

4. CI/CD 配置(GitHub Actions、GitLab CI)

这俩平台上 .yml 文件的标准格式都是 YAML。一个 GitHub Actions 工作流里有 on(触发器)、jobs(任务)、steps(步骤)三层结构,再加上每个 step 可能内嵌一段 shell 脚本,YAML 的多行字符串功能在这里体现得淋漓尽致。

常见误区与陷阱

误区一:YAML 就是”去掉花括号的 JSON”

这是初学者最容易犯的错误——觉得 YAML 就是 JSON 的简化版,可以无脑拷贝粘贴。实际上,YAML 的类型推断规则能在不经意间坑到你。

挪威问题(Norway Problem)

YAML 史上最著名的陷阱,没有之一。看这个例子:

countries:
  - no       # 本想写挪威的国家代码
  - gb

解析器看到 no,自动推断为布尔值 false——因为在 YAML 1.1 规范中,nofalse 的别名。这份配置的实际结果是 [false, "gb"]。你能想象一个新手遇到这种 bug 时的表情吗?“我没写 false 啊,怎么冒出来的?”

YAML 1.1 中会被误判的还有:yes/no/on/off → true/false,1.0.0 → 可能被当成字符串也可能被当成版本号(取决于解析器实现)。YAML 1.2 规范收敛了大部分这类问题,但市面上很多工具还在用旧版解析器。

解决方案很简单:不确定的时候,加引号。"no" 永远是字符串。

误区二:缩进随便对齐就行

我曾经写过一个 Ansible playbook,一个任务的 when 条件缩进多了一格——结果这个条件被解析为上层 block 的属性而非当前 task 的属性,整个执行逻辑完全跑偏。YAML 不会像编译器那样给你报”unexpected indent”,它默默地按自己的理解去跑,这才是最可怕的。

锚点与别名的”浅拷贝”陷阱

YAML 支持用 &anchor 定义锚点和 *anchor 引用它,减少重复配置:

defaults: &defaults
  timeout: 30
  retries: 3

service_a:
  <<: *defaults
  host: a.example.com

service_b:
  <<: *defaults
  host: b.example.com

好用归好用,但注意这是浅合并。如果锚点里有一个嵌套对象或数组,引用它的地方共享的其实是同一个引用。你在 service_a 里往那个数组 push 一个元素,service_b 里的数组也会多一个元素。这不叫 bug,这是设计如此——但知道的人不多。

YAML vs JSON vs TOML

YAMLJSONTOML
注释支持 #不支持支持 #
可读性高(人类优先设计)中(机器优先设计)高(INI 文件风格)
缩进敏感
类型推断激进(有时是陷阱)无推断保守
规范复杂度极高(2.4 万词规范)低(几页纸)
主要场景K8s、CI/CD、运维自动化Web API、数据交换Rust/Cargo、Python pyproject.toml

JSON 赢在简单和普适。它的规范只有几页纸,任何语言都有成熟的解析器,且解析速度极快。Web API 用 JSON 是历史选择也是理性选择。

TOML 走了一条中庸路线——比 JSON 可读性好,比 YAML 安全得多(没有类型推断的坑)。Rust 社区是 TOML 的主要推动力,Python 社区也逐步把 setup.py 迁移到了 pyproject.toml

YAML 的优势领域非常明确:需要人频繁手写、需要注释、结构以嵌套为主、且数据流不经过网络传输的配置文件。一旦跨过”配置文件”这个边界到了”数据交换格式”,JSON 立刻扳回一城。

常见问题

Q: JSON 和 YAML 互转会丢信息吗?

结构信息不会丢,但注释会。JSON 不支持注释,所以 YAML 里的注释在转成 JSON 时会被丢弃。反过来 JSON 转 YAML 不会有这个问题——JSON 本来就没有注释。

Q: 为什么不用 YAML 做 Web API 的响应格式?

两个原因。一是解析慢——YAML 规范是 JSON 的几十倍复杂,解析器体积和耗时都远超 JSON。二是安全风险——YAML 的某些解析器支持”标签”机制,可以构造恶意 YAML 触发任意代码执行(类似 Python pickle 的反序列化漏洞)。JSON 的语法极简,攻击面天然就小。

Q: YAML 文件多大才不合适?

超过 2000 行的单一 YAML 文件就开始失控了——缩进太深导致视觉对齐困难,锚点引用混乱导致难以追踪数据流向。Kubernetes 社区的建议是把资源拆分成多个文件,用 Kustomize 或 Helm 模板来管理。

Q: “---”和“…” 在 YAML 里是干什么的?

--- 标记一个文档的开始,... 标记一个文档的结束。一个 YAML 文件里可以塞多个文档,像这样:

---
name: document1
---
name: document2
...

大部分日常场景用不到多文档,但在 Kubernetes 里,你可以把 Deployment 和 Service 放在同一个 .yml 里用 --- 分隔,然后一条 kubectl apply -f 全部部署。

Q: 我该选 JSON 还是 YAML?

如果你的数据是机器写给机器读的——Web API、消息队列、数据库存储——JSON 是正确答案。如果你的数据是人写给机器读的——配置文件、CI/CD 工作流、运维脚本——YAML 不会让你后悔。至于两个都需要的情况(比如微服务的配置中心),保留 JSON 作为内部序列化格式,对外暴露的时候转成 YAML 让运维团队去改,是实践中比较常见的折中方案。