Skip to content
Back to Blog
informational

TOML 是什么?打败 YAML 的配置语言

2026-05-17 9 min read

TOML 到底是什么

TOML 代表 Tom's Obvious, Minimal Language。它的创建者,GitHub 联合创始人 Tom Preston-Werner,在 2013 年就对现有的配置格式感到厌倦。这种语言经过近八年的发展,才在 2021 年 1 月发布了第一个稳定版本 TOML v1.0.0。漫长的完善期足以说明其设计考虑得多么周全。 TOML 的核心是一种配置文件格式。它不是像 JSON 那样的通用数据序列化格式,也不是标记语言。它的全部目标是让人们易于读写,同时能明确地映射到哈希表——或者在你选择的语言中,你可能称之为字典、映射或对象。 一个最简单的 TOML 文件是这样的: ``` title = "My Application" version = "2.4.1" debug = false [database] host = "localhost" port = 5432 max_connections = 100 ``` 每个值都有一个显式类型。`"localhost"` 是一个字符串。`5432` 是一个整数。`false` 是一个布尔值——而不是字符串 `"false"`,不是 `0`,也不是 `null`。这种严格性正是其核心价值,也是开发者选择 TOML 的主要原因。你永远不会浪费时间去猜测你的端口号是否会因为某个 YAML 库的怪癖而被解析成字符串还是整数。

为什么 YAML 成为一个值得解决的问题

YAML 因其对人类友好而备受赞誉。对于小文件来说,这确实如此。但随着配置文件的增长,它的友好性会迅速消失,其设计选择开始显现弊端。 最臭名昭著的例子是挪威问题。在 YAML 1.1 中——许多解析器仍然默认的版本——两位国家代码 `NO` 被解析为布尔值 `false`。像 `country: NO` 这样的配置会悄无声息地破坏你的数据。这适用于各种大小写的 `yes`、`no`、`on`、`off`、`true` 和 `false`。YAML 1.2 修复了这个问题,但你无法总是控制你的工具使用哪个解析器版本。 然后是重要的空白字符。YAML 使用缩进来定义结构,所以一个放错位置的空格就能悄无声息地改变文件的整个含义,或者直接使其完全崩溃。任何曾花了一个小时调试 CI/CD 流水线,结果却发现是两格或四格缩进不一致的问题的人,都深知这种痛苦。 YAML 还提供了太多种方式来表达同一件事。标量可以是纯文本、单引号、双引号或块样式。序列可以是流式或块式。这种灵活性听起来不错,但这意味着两个开发者会以完全不同的方式编写相同的逻辑配置,使差异难以阅读,代码审查效率降低。 这并非让 YAML 变得毫无用处。它成为 Kubernetes manifest 和 GitHub Actions 工作流的标准是有原因的。但对于应用程序配置,正确性和可预测性比任何事情都重要,这些怪癖是一个严重的隐患。

TOML 如何解决可读性问题

TOML 的哲学很简单:只有一种显而易见的方式来编写某物。这听起来很严格,但结果是无论谁编写或属于哪个项目,配置文件看起来都一样。 它提供了六种标量类型:字符串、整数、浮点数、布尔值、带偏移的日期时间以及本地日期。TOML 对 RFC 3339 格式的日期和时间的一流支持是一个杀手级功能。你可以编写 `created_at = 2024-03-15T09:30:00Z`,并相信它会成为一个正确的日期时间对象,而不是你需要自己解析的字符串。虽然 YAML 可以表示日期,但不同解析器之间的行为不一致。 结构通过表(section)和表数组来定义。表使用方括号作为标题:`[server]`。表数组使用双层方括号:`[[products]]`。语法明确且易于识别。 这是一个来自 Rust `Cargo.toml` 文件的真实示例,展示了如何定义依赖项: ``` [dependencies] serde = { version = "1.0", features = ["derive"] } tokio = { version = "1", features = ["full"] } reqwest = "0.12" ``` 内联表语法——花括号内的部分——非常适合简洁紧凑的定义。对于更复杂的嵌套数据,则使用完整的表头。该语言为你提供了何时使用每种样式的清晰规则。 注释的设计也考虑到熟悉度。它们使用 `#` 字符,就像 Python 和 shell 脚本一样。大多数开发者无需阅读一行规范就知道这种语法。

TOML 在哪里赢得了胜利:真实的采用数据

Rust 生态系统是 TOML 最大的成功案例。Cargo,Rust 的包管理器,强制其 manifest 使用 TOML。截至 2025 年初,crates.io 上有超过 150,000 个包,每一个都有一个 `Cargo.toml` 文件。这是一个极少数格式通过的大规模真实世界压力测试。 Python 通过 PEP 518 (2016) 和 PEP 621 (2021) 采用 TOML,将 `pyproject.toml` 确立为项目元数据和构建配置的唯一标准位置。Poetry、Hatch、Flit 和 PDM 等工具都使用它。你的 linter 设置放在 `[tool.ruff]` 中,你的测试设置放在 `[tool.pytest.ini_options]` 中。你只需一个文件和一种格式来统领一切。 流行的静态网站生成器 Hugo 将 TOML 作为其默认配置格式,放弃了 YAML 和 JSON。该团队明确指出 TOML 的显式性以及不会出现令人意外的类型强制转换是其采用的动力。 当一种语言将其解析器添加到标准库中时,你就知道这种格式已经站稳脚跟了。Python 正是这样做的,在 3.11 版本(2022 年 10 月发布)中添加了 `tomllib`,让你可以在任何现代 Python 安装上解析 TOML 文件,而无需任何外部依赖。 这也不仅仅是 Python 和 Rust 的专属。Go、.NET 和 JavaScript 都有成熟且维护良好的 TOML 库。例如,npm 上的 `@iarna/toml` 包每周有数百万次下载。TOML 正式成为主流。

TOML 的真正局限性

没有哪个工具是完美的,TOML 也不例外。坦诚面对其局限性很重要,这样你才能知道何时应该选择其他工具。 深度嵌套数据是 TOML 的阿喀琉斯之踵(致命弱点)。如果你需要超过两三层的嵌套,语法就会变得很痛苦。你会发现自己写出很长的点分隔键,例如 `[servers.production.database.replica]`。这虽然有效,但可读性不佳。JSON 甚至 YAML 在这方面表现更好,因为它们就是为通用数据表示而构建的。 复杂对象的大型数组是另一个弱点。表数组的 `[[products]]` 语法意味着你必须为每个单独的项重复该标题。一个包含 50 个产品的列表将导致 50 个独立的 `[[products]]` 标题。到那时,你写的就不是配置文件了;你是在使用错误的工具。你应该使用 JSON 或数据库。 TOML 缺乏对锚点和别名的支持,这是 YAML 通过 `&anchor` 和 `*alias` 提供的功能。这意味着你无法定义一次设置块并在文件中重复使用。如果你需要重复配置,你有两种选择:直接复制,或者在你的应用程序代码中构建合并逻辑。没有内置的方法来保持 DRY (Don't Repeat Yourself) 原则。 最后,你不能流式处理 TOML 文件。与 JSON Lines 不同,TOML 文档必须完全读取和解析后才能访问任何值。这对于巨大的配置文件可能很重要,尽管这么大的配置文件通常是更深层设计问题的症状。 这些局限性并没有让 TOML 成为其预期用途(中等复杂度的应用程序配置)的糟糕选择。它们只是定义了它的边界。

TOML 与其他格式之间的转换

如果你正在从 YAML 迁移项目,或者只是需要为某个工具在不同格式之间转换配置数据,你有几种选择。 在 Python 中以编程方式转换,你可以将 `tomllib`(读取)和 `tomli-w`(写入)库与 `PyYAML` 等 YAML 解析器结合使用。将 YAML 文件读取到 Python 字典中,然后通过 `yaml.safe_load()` 和 `tomli_w.dumps()` 写入是可行的,但这最适合扁平或中等嵌套的文件。深度嵌套的结构、YAML 锚点或多文档文件将需要手动清理。 JSON 到 TOML 的转换,映射要干净得多,因为两种格式都有显式类型。JSON 整数会变成 TOML 整数。JSON 布尔值会变成 TOML 布尔值。主要的结构性转变是将 JSON 的对象数组转换为 TOML 的表数组(`[[table]]`),一个好的转换器可以处理这一点。 像 CocoConvert 这样的在线工具可以为你处理转换。上传 JSON 或 YAML 文件,选择 TOML 作为输出格式,转换器即可完成工作。对于简单的配置文件来说,这是一个很好的选择。对于包含 YAML 特定功能(如锚点或深度嵌套结构)的文件,你仍然需要审查输出。当 CocoConvert 发现有内容无法干净地映射到 TOML 时,它会标记转换警告,但它无法为你重构数据结构——那是你必须做出的决定。 对于批量迁移,例如转换包含 40 个 YAML 文件的整个仓库,一个接一个地上传是不可行的。CocoConvert API 就是为此而构建的,让你能够批量处理请求并自动化整个过程。

你的项目应该切换到 TOML 吗?

那么,你应该切换吗?答案取决于你的项目以及其工具链的预期。 对于 2025 年的新 Python 项目,答案是肯定的。使用 `pyproject.toml`。生态系统已经将其标准化,对抗它只会制造不必要的摩擦。不要成为那种在项目主配置文件中使用 `[tool.ruff]` 部分才是标准时,还创建单独的 `ruff.toml` 文件的人。 如果你正在编写 Rust 项目,这甚至不是一个问题。Cargo 使用 TOML,这是一个特性,而不是一个 bug。这种格式的一致性是 Rust 工具链如此协调的一个重要原因。 对于一个现有且 YAML 配置运行良好的项目,我只会建议在 YAML 成为痛点时才进行迁移。如果你的 `config.yaml` 稳定且团队理解它,那么切换的成本——更新文档、脚本和人们的习惯——可能不值得为了使用 TOML 的“纯粹性”而付出。 在 Kubernetes 和 CI/CD(GitHub Actions, GitLab CI 等)的世界里,YAML 仍然是王者。你别无选择,TOML 在这个领域也没有试图取代它。 真正的试金石是:你是否在 YAML 文件中写注释来解释某个值 *应该* 是什么数据类型?你是否在追查由于数字被读取为字符串而导致的 bug?这就是你的信号。你已经超越了 YAML 的模糊性。TOML 的设计初衷就是通过从一开始就明确类型来解决这类问题。