前言
在软件开发的世界里,配置文件无处不在。它们如同应用程序的“说明书”,指导着程序的行为。长期以来,JSON 以其简洁和广泛的机器支持,成为了许多配置场景的选择。然而,当配置文件需要由人来频繁阅读和修改时,JSON 的严格语法、缺乏注释等问题就显得不那么友好了。与此同时,YAML 虽然更注重人类可读性,但其复杂的规范(长达80页!)和一些“坑”(比如著名的“挪威问题”)也让不少开发者望而却步。
这时,一个旨在平衡简洁性、可读性和精确性的格式——TOML(Tom’s Obvious, Minimal Language)应运而生。它由 GitHub 联合创始人 Tom Preston-Werner 设计,目标是成为一种“显而易见、最小化”且易于阅读的配置文件格式。本文将结合社区讨论和实践经验,探讨为什么你应该考虑使用 TOML,它适合哪些场景,以及如何开始使用它。
什么是 TOML?
正如其名,TOML 的核心设计哲学是显而易见和最小化。它力求:
- 极易阅读: 语法设计贴近自然语言和传统的 INI 文件,减少认知负担。
- 最小化: 避免 YAML 那样的复杂特性和潜在歧义。
- 无歧义映射: 设计上可以明确、无歧义地映射到哈希表(或字典、对象),便于各种语言解析。
为什么选择 TOML?——TOML vs. JSON & YAML
-
可读性和易编写性
这是 TOML 最受推崇的优点。对比 JSON,TOML 省略了大量的引号和结尾逗号,顶层结构也无需花括号包裹,使得手动编辑更加流畅,错误更少。-
TOML 示例:
# 数据库配置 [database] server = "192.168.1.1" # 服务器地址 ports = [ 8001, 8001, 8002 ] # 端口列表 connection_max = 5000 # 最大连接数 enabled = true # 是否启用
-
等效 JSON (无注释):
{ "database": { "server": "192.168.1.1", "ports": [ 8001, 8001, 8002 ], "connection_max": 5000, "enabled": true } }
正如 Hacker News 讨论中提到的,JSON 如果加上注释和尾随逗号(类似 JSON5)会好很多,但这并非标准。TOML 原生就解决了这些痛点。
-
-
原生支持注释!
配置文件往往需要解释。TOML 使用#
进行行注释,这对于维护和理解配置至关重要。你可以清晰地说明每个配置项的用途、可选值或注意事项。标准 JSON 不支持注释,这是一个巨大的缺陷,迫使开发者使用变通方法(如添加"comment"
字段)或完全放弃注释。 -
更友好的语法和数据类型
- 字符串: 支持多种字符串表示(基本、多行、字面量、多行字面量),尤其多行字符串 (
"""..."""
) 对于包含长文本(如密钥、描述)非常方便,远胜于 JSON 中的\n
转义。 - 日期时间: 原生支持 RFC 3339 格式的日期时间,避免了在 JSON 中需要约定俗成或依赖时间戳的麻烦。
created_at = 1979-05-27T07:32:00Z
- 数字: 支持下划线 (
_
) 提高大数字的可读性 (1_000_000
),并支持不同进制表示。 - 裸键: 简单的键(字母、数字、下划线、破折号)无需引号。
- 字符串: 支持多种字符串表示(基本、多行、字面量、多行字面量),尤其多行字符串 (
-
清晰的层级结构
TOML 使用[table_name]
和[[array_of_tables_name]]
来定义层级。对于嵌套结构,这种方式通常比 JSON 密集的{}
和[]
更容易视觉追踪。[servers.alpha] ip = "10.0.0.1" dc = "eqdc10" [servers.beta] ip = "10.0.0.2" dc = "eqdc10" # 用户列表 (表数组) [[users]] name = "Alice" role = "admin" [[users]] name = "Bob" role = "user"
虽然 Hacker News 上有人指出 TOML 在处理深度嵌套对象列表时可能不如 JSON 直观(如
[[a]] [a.b] [[a.b.c]]
),但 TOML 1.1 引入的多行内联表 (tbl = { key = "value", ... }
) 在一定程度上缓解了这个问题。 -
相对 YAML 的简单与安全
YAML 功能强大,但也带来了复杂性和潜在风险。例如,“挪威问题”(no
被解析为false
)、不同版本间的解析差异、以及不安全加载可能导致的任意代码执行。TOML 的设计更为简单、明确,且没有代码执行风险,更适合作为安全的配置文件格式。
什么场景下应该使用 TOML?
TOML 的最佳应用场景是那些需要人类(包括开发者和最终用户)经常阅读、编写和维护的配置文件。
- 应用程序配置: 如数据库连接、服务端口、功能开关、主题设置等。
- 项目元数据: 如 Python 的
pyproject.toml
、Rust 的Cargo.toml
,用于定义项目依赖、构建信息等。 - 需要用户自定义的设置: 当你需要让非技术用户也能相对容易地修改某些参数时。
何时不一定选择 TOML?
- 机器间数据交换/API: JSON 仍然是事实上的标准,其广泛的原生支持和通常更快的解析速度使其成为首选。
- 极度复杂的嵌套数据结构: 如果你的配置结构非常深且复杂,JSON 或 YAML 的显式嵌套语法可能更直接(尽管 TOML 1.1 有所改进)。
- 需要序列化任意程序对象: YAML 的标签系统 (
!tag
) 提供了更强的对象序列化能力(但也更复杂和潜在不安全)。
如何使用 TOML?
-
学习基本语法:
- 键值对:
key = value
- 注释:
# comment
- 字符串:
"basic"
,'literal'
,"""multiline"""
,'''multiline literal'''
- 整数:
42
,1_000
,0xDEADBEEF
- 浮点数:
3.14
,6.626e-34
- 布尔值:
true
,false
- 日期时间:
1979-05-27T07:32:00Z
- 数组:
ports = [ 8001, 8002 ]
- 表(字典/对象):
[table_name]
- 嵌套表:
[parent.child]
- 表数组(对象列表):
[[array_name]]
- 内联表:
point = { x = 1, y = 2 }
(TOML 1.1 支持多行)
- 键值对:
-
选择合适的解析库:
TOML 拥有广泛的语言支持。赵子清的文章和 GitHub 仓库(如libtoml
,tomlc99
,tinytoml
,HyperTomlProcessor
,loltoml
,clj-toml
,toml.cr
,sawyer
等)都证明了这一点。几乎所有主流语言都有成熟的 TOML 解析库。例如:- Python:
tomllib
(标准库, 3.11+),toml
(第三方库) - Rust:
toml
crate (serde 支持) - Go:
BurntSushi/toml
,pelletier/go-toml
- JavaScript/Node.js:
@iarna/toml
,toml
- C/C++:
tomlc99
,toml++
,tinytoml
- Java:
toml4j
,TOML-javalib
- .NET:
Tomlyn
,HyperTomlProcessor
在你的项目中引入相应的库,然后通常只需调用类似
toml.load("config.toml")
或toml.parse(string_content)
的函数即可将 TOML 文件解析为你语言中的原生数据结构(如字典、列表等)。 - Python:
小结与思考
TOML 并非旨在取代 JSON 或 YAML,而是为特定的痛点——创建易于人类理解和维护的配置文件——提供了优雅的解决方案。它在 INI 的简洁性、JSON 的结构化和 YAML 的部分人类友好性之间取得了良好的平衡,同时避免了后两者的主要缺点。
正如 Hacker News 的讨论所揭示的,没有哪个格式是完美的。TOML 缺少 null
是一个有争议的设计决策(有人认为“键不存在”即是 null,有人则需要显式 null),其嵌套表示方式也有不同的看法。但总体而言,对于绝大多数配置场景,TOML 提供的清晰度、简洁性和安全性使其成为一个非常有吸引力的现代选择。
下次当你为新项目选择配置格式时,问问自己:这个文件主要是给谁看的?需要经常手动修改吗?如果答案是“人”,那么 TOML 极有可能就是你的最佳答案。
一个很大的原因就是,toml格式是支持注释的,因此人容易理解
参考资料: