Skip to content
Back to Blog
format-comparisons

YAML、JSON 与 TOML:如何选择配置文件格式

2026-05-17 9 min read

为什么配置文件格式的选择真的很重要

如果你选错了配置文件格式,就得花好几个小时去调试一个漏掉的逗号。你会和一个默默丢掉某个键的解析器作斗争。你还得一次又一次地向新团队成员解释那些晦涩难懂的缩进规则。这些都不是凭空想象。正是这些小问题日复一日、周复一周地消耗着项目的生命力。 在现代软件开发中,你几乎总会遇到 YAML、JSON 或 TOML。Docker Compose 使用 YAML,REST API 绝大多数使用 JSON,而 Rust 的包管理器 Cargo 选择了 TOML。这些选择中的每一个都反映了在人类可写性、机器可解析性和表达能力之间的深思熟虑的权衡。一个易于维护的项目和一个脆弱的项目之间的区别,往往就在于是否理解了这些权衡,而不是仅仅照搬第一个教程里用的格式。 我们将从真正重要的维度来比较这三者:语法、数据类型、注释、多行字符串和工具支持。我们还会讨论为什么你可能需要在它们之间进行转换,以及更重要的,这种转换过程的真正局限性。最好在开始之前就了解这些。

JSON:严格、可移植,但手写起来出奇的痛苦

JSON,即 JavaScript Object Notation,有一个决定性的特点:严格。它在 2017 年被标准化为 RFC 8259,但其灵魂源于 Douglas Crockford 在 21 世纪初的原始规范。任何给定的数据结构都只有一种确切的写法。键必须用双引号。末尾的逗号是禁止的。至于注释?根本不存在。至少在规范里没有。 在机器对机器的通信中,这种死板是优点,而不是缺点。JSON 的可预测性正是它征服 REST API 和构建工具世界的原因。每种主流语言都有一个标准库解析器,而且由于格式如此明确,你可以确信你发送的就是对方将收到的。它就是好用。 但当需要人来手写时,痛苦就来了。任何花过十分钟在一个巨大的 `webpack.config.js` 文件里寻找一个丢失逗号的人都懂这种感觉。一个简单的数据库连接配置块还算可以: ```json { "database": { "host": "localhost", "port": 5432, "name": "myapp_production" } } ``` 但如果把它扩展到 40 个键,嵌套三层深,然后忘掉一个逗号呢?解析器通常会直接放弃,给你一个指向文件末尾的神秘错误,而不是指出真正有问题的行。没有注释也同样令人沮丧。人们只能采取一些变通方法,比如 `"_comment": "这个设置控制缓存的 TTL"`,仅仅为了给未来的自己留个言,却污染了数据模型。 这就是为什么会出现像 JSON5 和 JSONC(带注释的 JSON)这样的格式。它们很受欢迎——VS Code 的 `settings.json` 就使用了 JSONC——但它们本质上是非标准的。别被骗了,以为它们是安全可靠的默认选项。如果你把一个 JSONC 文件发给一个标准的 JSON 解析器,它会直接崩溃。

YAML:可读性最强,陷阱也最多

YAML,全称是 YAML Ain't Markup Language(YAML 不是一种标记语言),它将人类可读性放在首位。它抛弃了 JSON 的大括号和引号,转而采用一种基于缩进的简洁结构。它原生支持注释(`#`),并且能优雅地处理多行字符串。用 YAML 编写的 GitHub Actions 工作流或 Kubernetes 清单,无疑比其对应的 JSON 版本更易于人类阅读。 看看同样的数据库配置在 YAML 中是什么感觉: ```yaml database: host: localhost port: 5432 name: myapp_production # 副本集于 2024-03-10 添加 replicas: - replica1.internal - replica2.internal ``` 它更整洁,而且解释变更的关键注释也成了一等公民。多行字符串也通过块标量——`|` 和 `>` 字符——得到了解决,这使得在配置中嵌入 SQL 查询或 shell 脚本成为可能,而不会变得一团糟、难以阅读。 但这种可读性是有代价的,这个代价就是模糊性和隐藏的危险。这些就是臭名昭著的 YAML “陷阱”。最著名的是“挪威国旗问题”,即国家代码 `NO` 在旧版 YAML(许多工具仍在使用!)中默认被解析为布尔值 `false`。缩进中一个错位的空格不会抛出错误,而是会悄无声息地改变你整个数据的结构。YAML 的规范本身长达 86 页——比 HTTP/1.1 的规范还长!这足以告诉你,在那看似简单的表面下隐藏了多少复杂性。 那么我们该如何选择呢?对于基础设施即代码(IaC)和 CI/CD 流水线这类整天由人来编写和审查配置文件的场景,YAML 的可读性通常值得冒这个险。但对于由代码生成且很少手动修改的配置,这个优势就消失了,你手里剩下的只有一把上了膛的枪。

TOML:务实的中间派

TOML,即 Tom's Obvious, Minimal Language(Tom 的清晰、最小化语言),是一个务实的选择。它由 GitHub 联合创始人 Tom Preston-Werner 创建,并于 2021 年最终确定为 1.0.0 版本。它存在的全部意义就是成为一个清晰、无歧义,同时读起来又很舒服的配置文件格式。 它看起来有点像老式的 INI 文件,用方括号来表示节标题,但它增加了明确的数据类型,这是它的杀手级特性。这种结构鼓励更扁平化的配置,这可能是一种有益的约束。 ```toml [database] host = "localhost" port = 5432 name = "myapp_production" # 副本集于 2024-03-10 添加 replicas = ["replica1.internal", "replica2.internal"] ``` 这一点上 TOML 直接解决了 YAML 最大的问题。这里没有模棱两可的裸字符串。`port = 5432` 是一个整数,`enabled = true` 是一个布尔值,`NO` 就只是字符串 'NO'。你永远不必担心某个值会被神奇地强制转换成你意想不到的类型。它还原生支持日期时间,包括 RFC 3339 时间戳,这对于任何处理过日期字符串的人来说都是一种巨大的解脱。 它的主要弱点在于表示深度嵌套或高度重复的数据。用于表格数组的 `[[section]]` 语法对于简单情况(如 Cargo 的 `[[bin]]` 条目)是可行的,但很快就会变得笨拙。如果你需要三四层嵌套,YAML 的缩进模型突然看起来就更具吸引力了。TOML 也刻意省略了像 YAML 的锚点和别名这样的功能,所以如果你需要在不同环境间减少重复,那就没辙了。大型 TOML 文件可能会变得非常重复。 它的采用率还不错,但并非无处不在。Python 在 3.11 版本中将 `tomllib` 加入了其标准库,这是对其投下的重要信任票。在此之前,你必须使用第三方包。如果你的团队深耕于 Rust、现代 Python 或 Go 的生态系统,TOML 是一个极好且舒适的选择。对于跨多种不同语言的项目,在决定使用之前,你最好先检查一下解析器的可用性。

正面交锋:特性对比表

让我们把所有的权衡都放在一个地方。下面是针对在实际配置文件中最令人头疼的特性进行的直接比较。 **注释:** YAML 和 TOML:支持,通过 `#` 行注释。JSON:不支持。规范里没有,永远不会有。接受现实吧。 **末尾逗号:** YAML 和 TOML:不适用,因为它们不使用逗号作为主要分隔符。JSON:禁止。这是导致令人抓狂的 git diff 和解析错误的典型来源。 **多行字符串:** YAML:通过块标量(`|`, `>`)提供了极好的支持,尽管处理末尾换行符的指示符(`-`, `+`)可能有点棘手。TOML:通过三引号字符串提供了良好的支持。JSON:糟透了。你只能手动添加 `\n` 转义符,既难看又容易出错。 **日期和时间类型:** TOML:支持,原生支持日期时间对象。YAML:在 1.2 版本中支持,但解析器的支持情况可能不一致。JSON:不支持。日期按惯例只是字符串,需要你自己去解析。 **锚点和别名(DRY 配置):** YAML:支持,使用 `&anchor` 和 `*alias`。这是一个强大但常常令人困惑的功能,用于减少重复。TOML 和 JSON:不支持。你要么重复自己,要么依赖于预处理步骤。 **模式验证:** JSON:支持,有 JSON Schema,一个成熟且广泛支持的标准。YAML:可以使用 JSON Schema,但需要特定工具(`ajv`, `yamale`)。TOML:这方面的工具要不成熟得多。这是一个明显的弱点。 **等效数据的文件大小:** 压缩后,JSON 是最紧凑的。对于人类可读的文件,YAML 和 TOML 相当。对于典型的配置文件,差异很少超过 10-15%,所以这不是一个主要的决定因素。 **解析速度:** 对于在应用启动时读取配置,三者都足够快。对于高吞吐量的序列化(每秒数千次操作),JSON 是无可争议的冠军,这要归功于像 `simdjson` 这样高度优化的库。

格式转换:哪些行得通,哪些行不通

你迟早需要在这些格式之间进行转换。也许你在迁移一个项目,或者从一个 JSON API 生成 YAML 配置,又或者需要把一个 TOML 文件喂给一个只认 JSON 的老旧工具。这种情况时有发生。 CocoConvert 可以通过其 JSON-to-YAML、YAML-to-JSON、TOML-to-JSON 和 JSON-to-TOML 转换器为你处理这些机械性的工作。对于只有基本数据类型的简单配置,转换是无损且快速的。只需在“转换”页面上传你的文件,选择目标格式,然后开始转换即可。 但是自动转换有其硬性限制。你需要意识到在转换过程中会丢失什么,因为这不是工具的 bug,而是格式之间根本性的不匹配。 **转换为 JSON 时会丢失注释。** JSON 根本没有注释的概念。就是这样。当你把一个带注释的 YAML 或 TOML 文件转换为 JSON 时,那些注释会永远消失。没有任何变通办法,这是 JSON 规范本身的限制。 **YAML 锚点会被展开,而不是保留。** 如果你的 YAML 文件使用 `&defaults` 和 `*defaults` 来保持不重复,这种结构将会丢失。转换器会展开这些锚点,这意味着输出的数据是相同的,但它会在各处被复制。最终的文件虽然能用,但可维护性会变差。 **TOML 的日期时间可能会变成字符串。** TOML 原生的 `created_at = 2024-01-15T09:30:00Z` 在 JSON 中没有直接的对应物。CocoConvert 会把它变成一个标准的 ISO 8601 字符串,这是正确的惯例。但是读取这个 JSON 的应用程序需要足够智能,知道应该将该字符串解析回日期对象。 **深度嵌套的 TOML 转为 YAML** 在结构上没有问题,但输出可能会让你感到意外。TOML 的 `[[array of tables]]` 语法会映射为 YAML 的映射序列,如果你不习惯看这种结构,可能会觉得有点奇怪。 在生产环境中使用转换后的输出之前,一定要进行审查。对一个文件进行往返转换(例如,TOML -> JSON -> TOML),然后快速 `diff` 一下,是发现任何意外情况的最佳方式。

做出决定:什么场景下用哪种格式

没有唯一的正确答案,但不要因此就认为选择不重要。对于何时使用哪种格式,已经出现了一些清晰的经验法则。 **什么时候使用 JSON:** 当配置是为机器而非人类设计时。如果它是由工具生成和消费的,并且人类很少手动编辑它,那么 JSON 的严格性就是一种美德。这就是为什么 `package.json` 和 `tsconfig.json` 是 JSON 格式。最高兼容性是目标。 **什么时候使用 YAML:** 当人类是主要受众时。对于 Kubernetes 清单、GitHub Actions 或 Ansible playbooks,可读性至关重要。这些配置 постоянно由人来编写和审查。是的,那些陷阱是真实存在的,但它们是可控的。我有一个不容置疑的建议:如果你使用 YAML,你*必须*在你的 CI 流水线中强制使用像 `yamllint` 这样的 linter。没有例外。 **什么时候使用 TOML:** 当你想要 YAML 的可读性,又不想有其危险的模糊性时。如果你的配置大多是扁平的,并且你的团队在一个有良好支持的生态系统(如 Rust 的 Cargo.toml 或现代 Python 的 pyproject.toml)中工作,那么 TOML 是一个绝佳的选择。它也是对你的应用程序的最终用户来说最友好的编辑格式,因为它的语法简单,解析器错误通常也很有帮助。 **当三者都不完美契合时:** 退一步问问自己,一个静态配置文件到底是不是正确的工具。简单的键值对通常可以放在环境变量中。对于有条件逻辑的真正复杂场景,试图将其硬塞进 YAML 或 TOML 是一个错误。你已经超出了它们的能力范围。是时候承认你需要一种真正的编程语言来处理你的配置了,无论是一种专门的配置语言如 Dhall 或 Starlark,还是一个简单的 Python 脚本。这是一个更大的投入,但总比用嵌套的 YAML 堆砌出一个怪物要好。 如果你在项目中期发现自己选错了格式,CocoConvert 可以帮你处理迁移。然而,选择哪种格式最适合你的团队,这个战略性的决定,仍然取决于你。

YAML、JSON 与 TOML:如何选择配置文件格式 | CocoConvert Blog