"YAML 还是 JSON?"这个问题本身大多问错了方向。两者描述的是同一类数据结构——映射、列表、标量——但服务的读者不同:JSON 给程序读,YAML 给人改。凌晨两点要改配置的是人,不是机器。把两者当成同一份信息的两种视图,而不是竞争对手,很多纠结就烟消云散了。这篇文章把它们并排摆在一起,让你看清各自在哪发力。
可读性:这份文件到底是给谁看的
最直观的差别先放出来。下面这个小小的服务定义,两种写法对照:
{
"service": "checkout",
"replicas": 3,
"ports": [8080, 9090],
"database": {
"host": "db.internal",
"tls": true
}
}
service: checkout
replicas: 3
ports:
- 8080
- 9090
database:
host: db.internal
tls: true
它们说的是同一件事。JSON 把结构摊开给你看:每个花括号、每个逗号、每个引号都是看得见的边界。YAML 把这些全去掉,只靠缩进。人扫一眼,YAML 更快;程序无所谓,反正都是解析器干活。
注释:JSON 根本没有这个功能
这是大家在配置里选 YAML 的头号原因。JSON 压根没有注释语法。你想记下"为什么超时设成 30 秒而不是 5 秒",要么滥用一个 "_comment" 字段,要么写进没人看的 wiki。
YAML 可以这么写:
timeout: 30 # 2026-04-11 那次延迟事故后调高的
retries: 2
这一行比它看起来的更有价值。注释让配置文件从一张快照变成和你未来自己的对话。
多行字符串:YAML 悄悄很强的地方
JSON 没有干净的办法塞进一段文字。你要么手动转义每一处换行,要么让它全挤成一行。YAML 给了两个操作符:
|(字面块)保留每一处换行,原样不动。>(折叠块)用空格把行连起来,写散文正合适。
script: |
#!/bin/sh
echo "starting"
./run.sh
description: >
一段可以自然跨行的人类可读说明,
不用跟格式死磕,
读起来也舒服。
试着用 JSON 可读地写一遍,你就明白它的好了。
锚点:YAML 的杀手锏,也是最锋利的边
YAML 能把一个块定义一次、到处复用,在真实配置里能砍掉大量复制粘贴:
defaults: &defaults
memory: 512Mi
cpu: 250m
worker:
<<: *defaults
replicas: 4
api:
<<: *defaults
replicas: 2
&defaults 是锚点,*defaults 是引用,意思是"把这个映射贴到这里"。<< 是合并键,把字段拉进来。JSON 没有等价物——你要么重复写,要么用程序生成文件。
隐式类型 vs 显式类型:坑新人的地方
JSON 的类型是显式的:想要字符串就加引号,想要数字就不加,没有猜测。
YAML 会猜,而且是从裸文本猜:
country: NO # 布尔值 false,不是字符串 "NO"
version: 1.20 # 数字 1.2,不是字符串 "1.20"
flag: yes # 布尔值 true
ratio: 1:20 # 六十进制,被解析成 80
国家代码、版本号、比率——这些都会被静默地重新解释。唯一安全的规矩是:拿不准就加引号。JSON 从来不让你操这份心。
解析速度:JSON 领先一个量级
纯解析性能完全不在一个级别。JSON 的语法极小——几条产生式规则——解析器可以是一个紧凑的状态机。YAML 的语法是庞大的规范,有锚点、标签、流式和块式、自定义类型,还要对每个标量嗅探类型。实际中,调优良好的 JSON 解析器比解析同样数据的 YAML 解析器常常快 一到两个数量级。
对一份启动时只读一次的配置文件,这无所谓;对每秒解析上百万份文档的热路径,这就致命了。
安全面:YAML 能执行东西
这是让很多人意外的一点。一份 YAML 文档不只是数据——它可以携带类型标签,告诉加载器去构造对象。经典的危险例子:
!!python/object/apply:os.system
- "rm -rf /"
一个不加限制地尊重标签的加载器(Python 里不带限制的 yaml.load)会高高兴兴地构造并运行它。解法是使用 yaml.safe_load,它拒绝除基础类型之外的一切。JSON 没有这种暗器——格式本身没有实例化对象的机制,所以 JSON 解析器天生安全。
如果你要解析来自不可信来源的 YAML,safe_load 不是可选项。它是"我读了一份配置"和"我跑了别人的代码"之间的分界线。
互转时会丢什么
两种格式可以互相转换,但这个转换有一个方向是有损的:
- JSON 转 YAML:保住全部数据,还多了可读性,但也继承了 YAML 的类型猜测。JSON 里的字符串
"1.20"稍不留神就会变成 YAML 里的数字。 - YAML 转 JSON:注释和锚点必然丢失。合并后的结果还在,但可复用定义和那些解释说明一去不回。
对照表
| 维度 | JSON | YAML |
|---|---|---|
| 人类可读性 | 啰嗦 | 干净,靠缩进 |
| 注释 | 无 | 原生支持 # |
| 多行字符串 | 别扭 | | 与 > 块 |
| 复用 | 无 | 锚点与引用 |
| 类型处理 | 显式 | 隐式,会吓你一跳 |
| 解析速度 | 极快 | 慢 10–100 倍 |
| 安全性 | 设计上安全 | 标签能跑代码,要用 safe_load |
| 生态位 | API / 数据交换 | 人改的配置文件 |
那到底用哪个
两个都用。程序跟程序对话的线上和磁盘上留 JSON;人需要读、需要推敲、需要改的文件用 YAML。一个常见又合理的套路是:用 YAML 写、校验,再在到达需要快和需要安全的东西之前编译成 JSON。
需要在两者间移动时,JSON ⇄ YAML 转换 两个方向都能处理。只是记得 YAML 转 JSON 之后复查一遍数字和布尔值——版本号是最常见的牺牲品。