该用哪个
有用的分界线不在技术上,在作者是谁。JSON 是程序写、程序读的:生成、传输、解析,全程没有人参与。YAML 是人手写的,而且半年后还要再读一遍。
这一个问题就能定下大部分场景。HTTP 接口返回 JSON,换成 YAML 没有任何好处,客户端本来就都自带解析器。CI 流水线、Kubernetes 清单、应用配置用 YAML,因为它由人维护,需要留一句话说明超时为什么是 45 秒。
- 机器之间传输 —— 用 JSON。更小,到处都能解析,没有歧义要处理。
- 人手改的配置 —— 用 YAML。能写注释、标点少,这就是它存在的全部理由。
- 要存储和查询的数据 —— 用 JSON。数据库能给它建索引,几乎没有数据库给 YAML 建索引。
- 任何不可信来源 —— 用 JSON。它的语法极小,能出错的地方也就少得多。
真正的差别
YAML 1.2 是 JSON 的超集:任何合法的 JSON 也是合法的 YAML。反过来不成立,而这些缺口正是转换会丢东西的地方。
| JSON | YAML | |
|---|---|---|
| 注释 | 完全不允许 | # 到行尾 |
| 结构靠什么 | 花括号和方括号 | 缩进,也可以用括号 |
| 字符串引号 | 必须加 | 可选,坑就在这 |
| 尾随逗号 | 报错 | 不涉及 |
| 重复的键 | 后者胜出,且不报错 | 严格解析器会报错 |
| 多行字符串 | 只能用 \n 转义 | | 和 > 块 |
| 锚点复用 | 没有 | &anchor 与 *ref |
JSON 转成 YAML
嵌套的数据能干净地转过去,而且结果确实更短 —— JSON 需要的那些标点,改由缩进承担了。数组和父级同一层缩进,这是通行写法,也是多数 linter 期望的样子。
注意 env 变成了什么:对象数组变成一串短横线,每个对象的第一个键跟短横线同行,其余的缩进在下面。Kubernetes 和 GitHub Actions 用的都是这个形状。
输入 JSON
{
"service": "api",
"replicas": 3,
"ports": [8080, 8443],
"env": [
{ "name": "LOG_LEVEL", "value": "debug" },
{ "name": "REGION", "value": "eu" }
],
"limits": { "cpu": "500m", "memory": "512Mi" }
}输出 YAML
service: api replicas: 3 ports: - 8080 - 8443 env: - name: LOG_LEVEL value: debug - name: REGION value: eu limits: cpu: 500m memory: 512Mi
/json-to-yaml/ 的真实输出。空对象和空数组会保留成 {} 和 [],而不是变成你根本看不见的空行。引号这个坑
这个坑真的会浪费时间,而且只在 YAML 这一侧咬人。在 JSON 里,"01234" 毫无疑问是字符串,因为字符串永远带引号。在 YAML 里引号是可选的 —— 于是解析器只能猜,而它是照着字符猜的。
从 JSON 转出去是安全的,因为工具知道自己拿到的是什么。任何会被重新读成数字或布尔的值,转出时都会补上引号:
输入 JSON
{
"zip": "01234",
"version": "1.10",
"enabled": "yes"
}输出 YAML
zip: "01234" version: "1.10" enabled: "yes"
手写 YAML 为什么会丢数据
还是这三个字段,改成手写、不加引号。这就是大家真正踩到的坑,而且没有任何提示:
- 前导零 —— 邮编、手机号、账号都会丢。
01234变成1234。 - 末尾零 ——
1.10变成1.1,版本号从此匹配不上。 - `yes` 和 `no` —— 我们的解析器保留成字符串,与 YAML 1.2 一致。更老的 1.1 解析器(包括 PyYAML 的默认行为)会把
no变成false。这就是挪威问题:国家代码NO变成了布尔值。
手写的 YAML
zip: 01234 version: 1.10 enabled: yes
解析成 JSON
{
"zip": 1234,
"version": 1.1,
"enabled": "yes"
}1.10 变成了 1.1,邮编现在是个算术结果。三条都是 /yaml-to-json/ 的真实输出。转一圈能剩下什么
JSON 转 YAML 再转回来是无损的,因为引号是工具替你写的。手写 YAML 转 JSON 再转回来就不是,而且损失发生在入口,早于任何转换。
所以安全习惯只有一条:YAML 里凡是可能被读成别的东西的字符串,都加引号。以文本形式存的数字是整个高危类别 —— 标识符、版本号、邮编、国家代码,以及任何带前导零的东西。
- 注释两个方向都留不住。JSON 没地方放它,所以转换会丢掉每一行
#。 - 锚点和别名会被展开,不会保留 —— 复用变成了重复。
- 键的顺序会保留,这件事对看 diff 的意义大于对正确性的意义。
去试试,或者继续读
- JSON 转 YAML会替你给有歧义的字符串加引号,输出可以放心再解析一次。
- YAML 转 JSON把手写的 YAML 贴进来,看看解析器实际读到了什么。
- JSON 能写注释吗大家把配置搬去 YAML 的另一个原因,以及有哪些替代做法。
- JSON 完全指南语法、类型,以及那些报错背后的规则。