跳到主内容
d.devtul.fun
EN
配置 · 2026-08-18

YAML vs JSON:什么时候该用哪个(以及为什么答案是"两个都用")

"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:注释和锚点必然丢失。合并后的结果还在,但可复用定义和那些解释说明一去不回。

对照表

维度JSONYAML
人类可读性啰嗦干净,靠缩进
注释无原生支持 #
多行字符串别扭| 与 > 块
复用无锚点与引用
类型处理显式隐式,会吓你一跳
解析速度极快慢 10–100 倍
安全性设计上安全标签能跑代码,要用 safe_load
生态位API / 数据交换人改的配置文件

那到底用哪个

两个都用。程序跟程序对话的线上和磁盘上留 JSON;人需要读、需要推敲、需要改的文件用 YAML。一个常见又合理的套路是:用 YAML 写、校验,再在到达需要快和需要安全的东西之前编译成 JSON。

需要在两者间移动时,JSON ⇄ YAML 转换 两个方向都能处理。只是记得 YAML 转 JSON 之后复查一遍数字和布尔值——版本号是最常见的牺牲品。

继续阅读