返回博客
Comparison 2026-04-30

JSON vs YAML vs TOML: 使用哪种配置格式

三种最常见配置格式的实用比较和用例。

配置格式看起来可以互换——直到某个解析器悄悄把 no 变成 false、截断一个64位ID,或者从配置文件里实例化任意对象。JSON、YAML和TOML主导着现代工具链,团队经历的大部分痛苦都来自不清楚每种格式究竟在哪里会变形。这是一篇机制层面的比较,不是口味之争。

三份规范,三种哲学

JSON由RFC 8259和ECMA-404定义:六种值类型、没有注释、语法可以写在一张餐巾纸上。YAML 1.2.2(2021)是一份约一百页的文档,描述了三个表示层、标签、锚点和多文档流。TOML 1.0.0(2021)介于两者之间:一种带类型、注释友好的格式,明确设计为可以无歧义地映射到哈希表。

规范的体量在实践中很重要。所有主流JSON解析器的行为基本一致。而两个YAML解析器可以对同一个文件合理地得出不同结果,因为包括PyYAML在内的许多库仍实现YAML 1.1语义,另一些则实现1.2。

JSON: 可预测,但数字有锋利的边缘

{

"port": 8080,

"debug": true,

"hosts": ["a.example.com", "b.example.com"]

}

JSON真正的风险藏在数字和重复键里:

  • 数字精度。 RFC 8259没有规定精度要求,但JavaScript把每个数字都解析为IEEE 754双精度浮点数。超过2^53 - 1的整数会静默丢失位数——这正是Twitter API同时提供 idid_str 的原因。64位标识符请序列化为字符串。
  • 重复键。 规范只说键名应当(SHOULD)唯一。大多数解析器保留最后一次出现,有些保留第一次。当校验器和执行器以不同方式解析同一文档时,这种分歧曾被用于真实的安全绕过。
  • 没有注释是刻意设计。 Douglas Crockford删除注释是为了防止基于注释的解析器指令。手工编辑的配置请使用JSONC(tsconfig.json 实际就是)或JSON5,并明确工具链接受哪一种——纯JSON解析器两者都会拒绝。

JSON适用于API载荷、锁文件,以及一切机器写入频率高于人类的场景。

YAML: 可读,但有一扇1.1形状的活板门

port: 8080

debug: true

hosts:

- a.example.com

- b.example.com

YAML 1.2把 noyesonoff 变成了普通字符串,但YAML 1.1解析器会把它们强制转换为布尔值——这就是著名的Norway问题:国家代码 NO 变成 false。1.1规则还会把 1:30 解析为六十进制整数90,把版本号 3.10 变成浮点数 3.1。由于你通常无法控制哪个解析器来读你的文件,运维规则是:给所有可能看起来像其他类型标量的字符串加引号。

还有两个机制值得敬畏:

  • 锚点与别名(&base / *base)能优雅地消除配置重复,但也让billion-laughs攻击成为可能:几个嵌套别名就能膨胀到数GB。解析不可信YAML时要设置别名限制,或者干脆拒绝别名。
  • 任意对象构造。 PyYAML的旧版 yaml.load() 可以从带标签的节点实例化任意Python类。永远使用 safe_load 或默认安全的解析器。

同时记住:缩进禁止使用制表符,一个多余的空格就能悄悄改变嵌套层级。如果生态强制你使用YAML(Kubernetes、GitHub Actions、Ansible),请搭配模式校验——yamllint加上schemastore.org的JSON Schema,能在CI阶段拦截大部分灾难。

TOML: 故意无聊

port = 8080

debug = true

hosts = ["a.example.com", "b.example.com"]

[database]

url = "postgres://localhost:5432/app"

[[server]]

name = "eu-1"

[[server]]

name = "us-1"

TOML的设计目标体现为具体的保证:

  • 每个值都有明确类型:字符串、整数、浮点数、布尔值、数组、表,以及RFC 3339日期时间——日期是一等公民,不需要字符串约定。
  • 重复键是硬性解析错误,而不是实现自由。
  • 缩进没有语义:结构来自 [table] 头和点号键,因此重新格式化永远不会改变含义。

代价是嵌套的工效学。深度嵌套的表数组很快变得冗长——这就是为什么Cargo和pyproject.toml用TOML很舒服,而Kubernetes清单不会。一个有用的启发式:如果配置嵌套超过三层,问题在于配置模式本身,而不是格式。

实践中的选择

  • 机器间数据、API、锁文件: JSON
  • 人工编辑的应用/工具配置: TOML
  • 生态强制的基础设施配置: YAML,务必配合模式校验并给歧义标量加引号
  • 需要注释但必须兼容JSON的配置: JSONC/JSON5,并明确写入文档

反复出现的错误

  • 把64位ID作为JSON数字输出,几周后才调试数据损坏
  • 不加引号的YAML值:国家代码、恰好全是数字的git SHA、3.10 这样的版本号
  • 在没有别名/深度限制的情况下接受用户提交的YAML
  • 假设所有解析器以相同方式处理重复键
  • 用字符串拼接而非序列化器手工生成这些格式

格式之间的转换

三种格式在交集处共享同一数据模型(映射、数组、标量),因此机械转换通常只在一个方向上无损。JSON转YAML总是可行;YAML转JSON会在锚点、多文档和非字符串键上失败;TOML转JSON会丢掉注释层,而注释往往是文件里最有价值的内容。把人类编辑的格式当作唯一事实来源,机器格式在CI中生成,并且永远不要往返转换:只单向转换、两个产物都提交、在评审中对比差异。

提交前先验证:把配置粘贴到 sdk.is/json-formatter 的JSON格式化工具或 sdk.is/yaml-validator 的YAML校验器,让解析器先发现问题。