跳转到主要内容

developer-tools

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

robinhood-projects4 分钟阅读

JSON 查看器、格式化工具与编辑器

在实时交互树中查看、编辑和格式化 JSON。支持内联值编辑、键名重命名、添加/删除节点、JSONC 注释以及一键导出——全部在浏览器中完成。

使用工具 →

CSV 查看器与智能表格

将任意 CSV 作为交互式智能表格打开:排序列、筛选行、实时编辑单元格,并导出为 CSV、XLS 或 XLSX。完全在浏览器中运行,无需注册。

使用工具 →

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

每个项目最终都会遇到配置问题。你需要存储数据库凭证、定义部署环境、描述 CI 流水线,或者连接依赖项——某人必须选择一种格式。JSON?YAML?TOML?这个选择看起来随意,直到它咬了你一口。

Docker Compose 文件是 YAML。package.json 是 JSON。Rust 的依赖清单(Cargo.toml)是 TOML。Python 打包(pyproject.toml)是 TOML。GitHub Actions 工作流是 YAML。这些不是随机的——每种格式都因具体原因在其领域赢得了采用之战。理解这些原因让你能做出有意识的选择,而不是默认"模板用什么就用什么"。

本文是一份决策指南。读完之后,你将了解每种格式的真实权衡,看到导致生产事故的确切 YAML 陷阱,并拥有清晰的选择框架。


共同祖先:INI 文件

在深入研究现代竞争者之前,值得用三十秒了解一下 INI 文件——这三种格式在某种意义上都是对它的回应。

INI 文件长这样:

[database]
host = localhost
port = 5432
name = mydb
 
[server]
port = 8080
debug = true

简单、人类可读、支持节、支持注释。问题同样简单:没有标准规范(每个解析器对边缘情况的解释不同),没有超过一层的嵌套结构,没有数组,没有显式类型。一切都是字符串,除非你的应用另行决定。

INI 在 1997 年用于 php.ini 很好。用于 2026 年描述 Kubernetes 部署就不行了。但它确定了正确的目标——人类可编辑性和注释——这些目标被现代格式继承或抛弃了。


JSON:数据的通用语言

JSON(JavaScript Object Notation)在 2000 年代初标准化,成为 API 的主流 wire 格式。它的优势来自它的约束。

JSON 做对的地方

JSON 有且只有六种值类型:字符串、数字、布尔值、null、数组和对象。规范无歧义。每个合规的 JSON 解析器都会以相同方式解析同一个文档。没有惊喜。

{
  "database": {
    "host": "localhost",
    "port": 5432,
    "ssl": true
  },
  "allowed_origins": ["https://example.com", "https://api.example.com"],
  "timeout_ms": null
}

port 的类型无歧义是数字。ssl 无歧义是布尔值。allowed_origins 无歧义是字符串数组。JSON 解析器无法误读这个。

JSON 也无处不在。每种语言都有标准库 JSON 解析器。浏览器原生解析它。PostgreSQL 等数据库有原生 JSON 列。它是不共享运行时的系统之间数据交换的最小公分母格式。

JSON 在配置方面做错的地方

JSON 没有注释语法。这是 Douglas Crockford(JSON 的创建者)的刻意设计决定,理由是数据格式中的注释会鼓励人们在其中放置解析指令,这会破坏格式的可移植性。

这对 API 响应来说是很好的原则。对配置文件来说是严重的问题。配置文件需要注释。你需要解释为什么 timeout_ms 是 30000 而不是 10000。你需要标记哪些字段可以安全修改。你需要留下一条注释说"在 staging 上不要把这个设置超过 100"。

变通方法都很糟糕。一些团队使用 "_comment" 字段。一些使用 JSON5(带注释的超集)并希望他们的工具能处理它。一些使用 JSONC(带注释的 JSON,由 VS Code 的 settings.json 推广)。这些都不是标准 JSON。

JSON 在配置上也很冗长。不允许尾随逗号,在重新排序字段时会导致不断的语法错误。每个字符串键都需要双引号。没有多行字符串语法——一个长 shell 命令必须转义到一行上。

JSON 属于: API 响应、服务间数据交换、主要由机器写入的包清单(package.json、composer.json),以及任何需要跨异构系统保证解析一致性的地方。

你可以在线验证 JSON 以在错误到达应用之前发现语法错误——这比你预期的更重要,因为单个缺失的逗号或多余的括号会完全破坏 JSON 解析器。


YAML:力量与危险

YAML(YAML Ain't Markup Language)被设计为对 XML 友好的人类替代方案。它富有表现力、可读性强,在 DevOps 工具中广泛使用。它也是常用配置格式中最危险的。

YAML 做对的地方

YAML 允许注释(#)。它从不带引号的值推断类型,所以你可以写 port: 5432 而不是 "port": 5432。它支持带有类 heredoc 语法的多行字符串。它很简洁:缩进取代了大括号和方括号。

database:
  host: localhost
  port: 5432
  ssl: true
 
allowed_origins:
  - https://example.com
  - https://api.example.com
 
# 这是一个解释为什么超时时间高的注释
timeout_ms: 30000

对于 CI/CD 配置——GitHub Actions、GitLab CI、CircleCI——YAML 带注释清晰表达嵌套结构的能力确实有用。GitHub Actions 工作流文件经常跨越数百行,带有嵌套条件、矩阵策略和环境变量块。JSON 会不可读。TOML 对深度嵌套结构会很笨拙。

YAML 的陷阱

YAML 的类型推断是出问题的地方。解析器查看不带引号的值并决定它们的类型。它做出的决定并不总是你所期望的。

挪威问题。 在 YAML 1.1(大多数解析器实现的版本——不是 YAML 1.2)中,裸 NO 被解析为布尔值 false。no、No、n、N、false、False、FALSE、off、Off 和 OFF 也一样。ISO 3166-1 国家代码列表包含挪威的 NO。带有国家代码列表的配置文件会悄悄地把挪威变成 false。

# YAML 1.1——解析器实际看到的
country: NO        # → false(布尔值)
enabled: yes       # → true(布尔值)
mode: on           # → true(布尔值)
debug: off         # → false(布尔值)

解决方法是把所有应该是字符串的东西加引号:country: "NO"。但 YAML 的全部吸引力在于不必加引号。要求加引号来避免惊喜会削弱该格式的可读性优势。

八进制整数。 在 YAML 1.1 中,以 0 开头的数字被视为八进制。文件权限通常用八进制表示:chmod: 0755。YAML 解析器将 0755 读作十进制数 493,而不是 755。这已经破坏了真实的部署脚本。

# YAML 1.1——八进制解释
file_mode: 0755    # → 493(八进制 0755 的十进制等价)

多行字符串歧义。 YAML 提供多种多行字符串语法——字面块(|)、折叠块(>)和内联。它们在换行符、尾随空白和缩进方面的行为不同。使用错误的会静默地产生错误的字符串。

# 字面块——保留换行符
command: |
  npm install
  npm run build
 
# 折叠块——换行符变成空格(适用于长文本)
description: >
  这是一段长描述,将被
  折叠成一行,用空格连接。

制表符 vs 空格。 YAML 不允许用制表符缩进。静默插入制表符的编辑器会产生解析错误——或者更糟,根据制表位设置产生不同的解析结果。

YAML 1.2 修复了许多这些问题(包括布尔值列表),但大多数工具仍然实现 1.1。当你使用 YAML 时,你是在为你的工具链恰好带的解析器写代码,这可能与也可能不与你的假设相符。


TOML:专为配置设计

TOML(Tom's Obvious Minimal Language)由 GitHub 联合创始人 Tom Preston-Werner 于 2013 年创建,专门用作人类可以编写、机器可以无歧义解析的配置文件格式。

TOML 做对的地方

TOML 有明确、无歧义的类型。字符串总是带引号。数字总是数字。布尔值总是 true 或 false(小写,无别名)。没有从不带引号的值进行类型推断,所以没有挪威问题。

[database]
host = "localhost"
port = 5432
ssl = true
 
allowed_origins = [
  "https://example.com",
  "https://api.example.com",
]
 
# 这是一个解释为什么超时时间高的注释
timeout_ms = 30000

TOML 对日期和时间有一级支持(publishedAt = 2026-04-04),而 JSON 和 YAML 将其留作应用程序自行解析的字符串。

节头([database]、[[servers]])明确且可读。点号键(database.host = "localhost")让你内联表达嵌套值。表数组([[servers]])给你清晰的对象列表语法。

[[servers]]
name = "production"
host = "prod.example.com"
port = 443
 
[[servers]]
name = "staging"
host = "staging.example.com"
port = 443

TOML 规范精确且有版本控制。每个合规的 TOML 解析器对相同输入产生相同结果。

TOML 的不足之处

TOML 对浅到中等嵌套效果很好。对于深度嵌套结构——Kubernetes 清单或 Ansible playbook 中出现的那种——节头语法会变得重复。YAML 基于缩进的嵌套可以更自然地扩展到六七层深度。

TOML 的工具支持也不如 JSON 或 YAML。大多数语言有优秀的 TOML 库,但编辑器支持(语法高亮、验证、自动补全)不如 JSON 一致。


对比表

特性JSONYAMLTOMLINI
注释无有(#)有(#)有(; 或 #)
显式类型有无(推断)有无(全是字符串)
多行字符串无(仅转义)有(多种语法)有(""")无
嵌套结构有(冗长)有(简洁)有(中等)仅一层
解析歧义风险低高极低中
人类可写性中等高高高
机器生成优秀良好良好差
常见用途API、清单CI/CD、编排项目配置旧应用配置

决策框架

一旦去掉营销语言,选择归结为三个问题。

这主要是机器生成的还是人类编写的?

如果文件由代码生成并由代码消费——API 响应、构建产物、序列化缓存条目——使用 JSON。它最可移植、最易解析,工具支持是通用的。不需要在没有人类阅读的文件中写注释。在通过网络传输之前压缩这些 JSON 文件可以有意义地减少有效载荷大小。

如果人类定期编写和编辑这个文件——项目配置、环境描述、工具清单——注释是不可协商的。这就排除了纯 JSON。

你控制工具链吗?

如果你被锁定在一个要求特定格式的生态系统中——Kubernetes 要求 YAML、GitHub Actions 要求 YAML、Cargo 要求 TOML、package.json 是 JSON——没有什么可决定的。使用生态系统期望的格式。

如果你从头开始设计一个新工具或新配置文件,你有真正的选择。

嵌套有多深,你的团队对细微 bug 的容忍度如何?

YAML 富有表现力但脆弱。仔细编写 YAML、为所有字符串加引号、运行 linter、理解 1.1 与 1.2 区别的团队可以安全使用它。快速推进、编辑器设置多样、不在 CI 中运行 YAML linting 的团队最终会在生产中遇到挪威问题或八进制 bug。

TOML 是在你控制格式时人类编写配置的更安全选择。显式类型意味着解析器错误在解析时就会出现,而不是在运行时——那时挪威已经变成了 false,你的功能标志逻辑悄悄地出错了。

快速参考

  • API 响应或服务间数据交换: JSON
  • 包清单(主要由机器写入): JSON
  • Kubernetes、Docker Compose、GitHub Actions、Ansible: YAML(没得选)
  • 你正在设计的新项目配置文件: TOML
  • Rust 项目: TOML(Cargo.toml)
  • Python 项目: TOML(pyproject.toml)
  • 有简单键值需求的旧应用配置: INI(或迁移到 TOML)

验证配置文件

配置文件中的语法错误是最令人沮丧的 bug 之一——应用无法启动、错误消息晦涩难懂,而错误可能只是一个错位的字符。

对于 JSON,你可以在线验证并美化打印 JSON 以立即发现错误。在提交之前将 JSON 配置文件粘贴到验证器中需要五秒钟,可以防止一类 CI 失败。对于更深入的 JSON 工作流——为生产环境压缩、比较环境之间的配置变化——请参阅我们关于如何验证、压缩和美化打印 JSON 的指南。

对于 YAML,yamllint 是标准 CLI linter。如果你的团队编写生产 YAML,在 CI 中运行它不是可选的。对于 TOML,大多数语言生态系统都附带内置验证器(cargo check 隐式验证 Cargo.toml;python -c "import tomllib; tomllib.load(open('pyproject.toml', 'rb'))" 直接验证任何 TOML 文件)。


格式是一个契约

无论你选择哪种格式,它都成为与项目中每位开发者和工具的契约。以后更改它会很痛苦——不是不可能,但很痛苦。Python 生态系统从 setup.cfg 和 setup.py 迁移到 pyproject.toml 花了数年时间,至今仍未完成。

有意识地选择。当机器是主要消费者且可移植性至关重要时用 JSON。当生态系统要求或深度嵌套结构需要可读时用 YAML。当人类编写文件、正确性重要、且你希望解析器在读取时捕获歧义而不是六个月后作为运行时 bug 出现时用 TOML。

配置格式不仅仅是一种文件格式。它是关于谁编辑文件、什么工具处理它,以及你对隐式类型强制转换信任程度的声明。有意识地做这个声明。


现在需要验证一个 JSON 配置文件?试试 FileCrank JSON Viewer——粘贴你的 JSON,立即发现错误,并美化打印以提高可读性。