
大家好,我是若风。
上周在配置一个 Rust 项目的时候,我盯着 Cargo.toml 发了一会儿呆。然后突然意识到一件事:我写了这么多年代码,跟配置文件打交道的时间可能比写业务逻辑还多。package.json、docker-compose.yml、tsconfig.json、.gitignore、terraform.tf......每个项目至少 3 到 5 个配置文件。
但说实话,我从来没认真想过一个问题:为什么这些工具要用不同的配置格式?
YAML 写 Kubernetes 配置,JSON 写 package.json,TOML 写 Cargo.toml,HCL 写 Terraform------这些选择是随机的,还是各有道理?
带着这个疑问,我做了一次深度调研。这篇文章就是调研结果。
先说结论
如果你在为新项目选配置格式,2026 年的默认推荐是 TOML,除非你遇到以下情况:
- 场景是数据交换------用 JSON
- 工具链强制要求 YAML(如 Kubernetes、GitHub Actions)------用 YAML
- 你在写 Terraform------用 HCL
下面展开讲为什么。
TOML 是什么?
TOML 的全称是 Tom's Obvious, Minimal Language------Tom 的显而易见的极简语言。注意,它不是递归缩写,也不是回文;只是一个名字很直白的配置格式。
创建者是 GitHub 联合创始人 Tom Preston-Werner,最早在 2013 年公开。它的设计动机很直接:做一个比 INI 更有类型、比 YAML 更少歧义、比 JSON 更适合人手写的配置格式。
具体来说,Tom 忍不了这几件事:
YAML 规范和生态都比较复杂,很多问题来自旧版 YAML 1.1 规则以及解析器兼容行为:比如 NO、yes、on、off 可能被解析成布尔值,1:30 在部分旧规则里可能被当作 sexagesimal 数字处理。YAML 1.2 已经收紧了这些隐式类型规则,但现实世界里的解析器和工具链并不总是严格按 1.2 行为走。同一份 YAML 文件在不同语言、不同解析器、不同版本下可能产生不同结果,这是它最让人头疼的地方。
Tom 的目标很清晰:做一个更好版本的 INI。语义明显、无歧义、最小化。能无歧义地映射到哈希表。
TOML 的核心特性
TOML 有一套丰富且严格的类型系统,这是它区别于 INI 的核心优势。
八种数据类型
toml
# 字符串------支持四种写法
title = "TOML Example" # 基本字符串(支持转义)
path = 'C:\Users\name\docs' # 字面量字符串(原样输出,不转义)
description = """可以换行的
多行字符串"""
regex = '''I [dw]on't need \d{2} apples''' # 多行字面量字符串
# 整数------支持进制转换和下划线分隔
port = 8080
hex_color = 0xDEAD # 十六进制
permissions = 0o755 # 八进制
big_num = 1_000_000 # 下划线分隔,提升可读性
# 浮点数------支持 inf 和 nan
pi = 3.14159
special = inf
# 布尔值------只有 true/false,不会搞出什么 yes/no/on/off
debug = true
# 日期时间------四种类型,是 TOML 相对 JSON/INI 更完整的地方
created = 2023-01-11T08:30:00Z # 带时区
deadline = 2023-12-31T23:59:59 # 不带时区
birthday = 1990-05-27 # 纯日期
alarm = 07:30:00 # 纯时间
集合类型
toml
# 数组------v1.0/v1.1 允许混合类型,但配置实践中更建议保持同构
ports = [80, 443, 8080]
mixed = ["admin", 42, true] # 合法,但不一定推荐
# 表(Table)------即字典/哈希表
[database]
server = "192.168.1.1"
ports = [8001, 8001, 8002]
# 嵌套表
[database.connection]
max_open = 100
# 内联表------单行紧凑写法
point = { x = 1, y = 2 }
# 数组表------用双方括号,每个块是数组中的一个元素
[[products]]
name = "Hammer"
sku = 738594937
[[products]]
name = "Nail"
sku = 284758393
这些特性覆盖了配置文件的几乎所有需求。跟 INI 比,功能丰富太多了;跟 YAML 比,每一条的语义都是明确的,没有隐式推断。
TOML 的版本演进
TOML 从 2013 年到现在,经历了一个从快速迭代到稳定规范的过程:
| 版本 | 时间 | 关键变化 |
|---|---|---|
| v0.1.x | 2013 年 | 初始阶段,确立键值对、表、数组等基础语法 |
| v0.2.x | 2013 年 | 补充日期时间、多行字符串、数组表 [[table]] 等能力 |
| v0.3.x | 2014-2015 年 | 引入点分隔键、内联表等更完整的结构表达 |
| v0.5.0 | 2018 年 | 允许混合类型数组,补充十六进制、八进制、二进制整数 |
| v1.0.0 | 2021 年 1 月 11 日 | 首个稳定版,成为多数生态的兼容基线 |
| v1.1.0 | 2025 年 12 月 18 日 | 当前最新规范 ,增加少量语法便利性,例如 \e、\xHH 转义,以及部分日期时间场景可省略秒 |
截至 2026 年 5 月,TOML v1.1.0 是当前最新规范 。不过很多语言标准库和主流工具仍以 v1.0.0 为兼容基线,例如 Python 3.11+ 标准库 tomllib 明确解析 TOML 1.0.0。因此实际选型时可以这样理解:v1.0.0 是最稳的跨生态底座,v1.1.0 是最新规范方向。
谁在用 TOML?
TOML 的采用不是靠营销,而是靠一个个具体的技术决策。
Rust:Cargo.toml
TOML 与 Rust 生态有很深的渊源。Cargo 使用 Cargo.toml,也是 TOML 获得广泛关注的重要起点。Rust 社区偏好显式和可预测的行为,跟 TOML 的"无歧义"理念很匹配。
Python:pyproject.toml
这是近十年最重要的配置格式迁移案例之一。Python 社区通过一系列 PEP 和 PyPA 规范,把构建系统依赖、项目元数据和工具配置逐步集中到 pyproject.toml。但它不是"一夜之间替代所有遗留文件":setup.py、setup.cfg、tox.ini 等文件在大量项目里仍然存在。
迁移路径大致是这样的:
arduino
阶段一(2016 前):setup.py + requirements.txt + MANIFEST.in + tox.ini + ...
阶段二(2016-2020):pyproject.toml 先标准化 [build-system],同时保留 setup.py/setup.cfg
阶段三(2020-2024):PEP 621 标准化 [project],更多构建后端和工具迁移进 pyproject.toml
阶段四(2024-2026):pyproject.toml 成为新项目的主配置入口,但遗留文件仍大量存在
Ruff 的成功不只是因为配置文件,但 [tool.ruff] 这种集中式配置确实降低了 Python 项目的工具链复杂度。过去 flake8、isort、Black、mypy、pytest 往往各有配置入口,现在越来越多工具支持把配置放进 pyproject.toml 的 [tool.*] 命名空间里。
其他采用者
| 项目 | 配置文件 | 说明 |
|---|---|---|
| GoReleaser | .goreleaser.toml |
Go 项目发布自动化 |
| Hugo | config.toml |
静态网站生成器 |
| Zola | config.toml |
静态网站生成器 |
| Poetry | pyproject.toml |
Python 包管理 |
| Ruff | ruff.toml |
Python linter/formatter |
| Taplo | --- | TOML 工具链(LSP、格式化、验证) |
五种格式全面对比
现在进入正题:TOML、YAML、JSON、INI、HCL,到底怎么选?
总览对比
| 维度 | TOML | YAML | JSON | INI | HCL |
|---|---|---|---|---|---|
| 创建年份 | 2013 | 2001 | 2001 | ~1980s | 2014 |
| 规范复杂度 | 中等 | 高 | 低 | 无统一规范 | 中高 |
| 注释 | # |
# |
不支持 | # 或 ; |
# 和 // |
| 类型系统 | 丰富 | 最丰富 | 基础 | 无 | 丰富 |
| 缩进敏感 | 否 | 是 | 否 | 否 | 否 |
| 日期时间 | 原生支持 | 支持 | 不支持 | 不支持 | 不支持 |
可读性排名
YAML > TOML > INI > HCL > JSON
YAML 视觉上最干净,但"看起来简单"不等于"读起来准确"------隐式类型推断是最大的隐患。TOML 结构清晰,[section] 分区直观,类型表示明确。JSON 的引号和逗号噪音太多,作为人类编辑的配置格式体验最差。
解析速度排名
JSON 通常最快,TOML/YAML/HCL 取决于实现和配置复杂度。
JSON 的语法最小,标准库实现也最成熟,所以通常是解析性能基准。TOML 比 JSON 语法更丰富,解析一般会慢一些,但配置文件通常很小,这个差距很少成为选型主因。YAML 因为语法、锚点、标签和隐式类型规则更复杂,解析和安全边界更难控制。HCL 的重点不在"最快解析",而在表达 Terraform 这类领域配置时的结构和表达式能力。
更实际的判断是:如果你在解析 MB 级甚至 GB 级机器生成数据,选 JSON 或二进制格式;如果只是人手写的项目配置,解析性能通常不如可维护性重要。
人类可写性排名
YAML ≈ TOML > INI > HCL > JSON
YAML 写起来最"自由",但也最容易写错。TOML 写起来有约束但可预测------主要痛点是深层嵌套时需要写很多 [section.subsection.deep] 头部。JSON 写配置文件的体验就不用说了,不能写注释这一条就够劝退了。
每种格式的典型陷阱
这部分很实用。每种格式都有自己独特的坑,踩过才知道疼。
TOML 的坑
重复定义和隐式表规则容易踩坑。 TOML 不允许同一个键重复定义,也不允许把已经定义成具体值的键再当表使用。点分隔键可以隐式创建表,但乱序写 dotted key 虽然可能合法,却会降低可读性。
内联表不可扩展。 point = { x = 1, y = 2 } 定义之后,不能再写 point.z = 3。
深层嵌套冗长。 这是 TOML 最被诟病的设计问题。五层嵌套的配置写起来会变成 [a.b.c.d.e],跟 YAML 的缩进比确实不够优雅。
YAML 的坑
"挪威问题"------旧版 YAML 规则和解析器兼容行为里的经典陷阱。
yaml
countries:
- NO # 在 YAML 1.1 / 部分解析器中可能被解析为 false
- SE # 通常是字符串 "SE"
- yes # 在 YAML 1.1 / 部分解析器中可能被解析为 true
- on # 在 YAML 1.1 / 部分解析器中可能被解析为 true
严格说,YAML 1.2 已经把布尔值收敛到更接近 JSON 的 true / false 规则;但很多工具为了兼容旧生态,仍可能保留 YAML 1.1 的隐式布尔行为。这个问题在涉及国家代码、开关名、环境变量值的配置中会突然冒出来,排查起来非常痛苦。
隐式数字解析也容易误伤。 旧规则和部分解析器可能把 12:30 当作 sexagesimal 数字处理,1.0 也可能被当作浮点数而不是字符串。凡是 ID、版本号、时间片段、国家代码这类值,在 YAML 里都建议显式加引号。
安全风险。 在 PyYAML 等库中,使用不安全 Loader 解析不可信 YAML 有反序列化风险。处理外部输入时应该使用 safe_load() 或等价的 SafeLoader;这条规则被人忘了无数次。
JSON 的坑
不支持注释。 这一条就让 JSON 在配置文件场景中极不实用,直接催生了 JSONC 和 JSON5 等超集格式。
尾随逗号禁止。 { "a": 1, "b": 2, } 是语法错误。改配置时加了一行就忘了删逗号,这种低级错误每个人至少犯过 3 次。
不支持真正的多行字符串。 长文本只能写成一行或用 \n 转义,可读性会明显变差。
INI 的坑
无标准规范。 Python 的 configparser、PHP 的 parse_ini_file、Windows 的 GetPrivateProfileString 行为各不相同。同一份 INI 文件在不同环境下可能解析出不同的结果。
无类型系统。 所有值都是字符串,需要应用层自己做类型转换。
选型建议:一张决策图
javascript
你的配置文件用途是什么?
│
├─ 数据交换/API 通信 → JSON(默认选择)
│
├─ 基础设施即代码(Terraform)→ HCL
│
├─ DevOps/CI/CD/Kubernetes → YAML(生态强制要求时遵循工具)
│
├─ 应用/项目配置文件
│ ├─ 新项目自由选择 → TOML
│ ├─ 已有工具链要求 → 遵循工具要求
│ └─ 极简配置(几个键值对)→ TOML 或 INI
│
└─ 需要编程逻辑(循环、条件)→ HCL 或考虑 DSL
业界趋势
最后聊聊配置格式的发展方向。
TOML 持续崛起
TOML 正在经历"慢热式"的广泛采用。不是爆发式增长,而是由一个个具体的技术决策推动:Rust 生态的示范效应、Python 包装生态对 pyproject.toml 的持续标准化、以及 Ruff、Poetry、Hatch、uv 等工具的采用,都在扩大 TOML 的日常可见度。编辑器工具链也成熟了------Taplo(TOML LSP)提供自动补全、验证、格式化,开发体验已经比较完整。
"YAML 疲劳"蔓延
DevOps 和云原生社区对 YAML 的不满正在积累。Kubernetes 生态里已经出现了 CDK8s、Pulumi 等用真正编程语言生成配置的替代方案。复杂 YAML 模板(如 Helm Charts)的可维护性问题日益突出。
但 YAML 的生态惯性极强------Kubernetes、GitHub Actions、GitLab CI 等核心工具仍然强制使用 YAML,短期内不会改变。
JSON 超集兴起
JSON 不支持注释这个根本缺陷,催生了 JSONC(VS Code 配置广泛使用)和 JSON5(更宽松的语法)等超集。在 TypeScript 和前端工具生态中,这类"类 JSON 配置"已经很常见。
配置即代码
一个更深层的趋势:当配置复杂到一定程度时,静态的声明式格式都不够用,需要编程语言的抽象能力。Pulumi 用 TypeScript/Python/Go 定义基础设施,AWS CDK 用编程语言生成 CloudFormation 配置,Dhall 和 CUE 是专门设计的可编程配置语言。
写在最后
回头看看这五种配置格式,有一个感受很深:没有完美的配置格式,只有最适合场景的选择。
JSON 是数据交换的通用语言,但不是好的配置格式。YAML 是 DevOps 领域的事实标准,但设计缺陷正在累积技术债。INI 是历史遗留的极简格式,新项目不建议使用。HCL 是基础设施即代码的专用工具,不适用于通用场景。
而 TOML 的设计哲学------"语义明显、最小化、无歧义"------在配置文件领域代表了最健康的平衡点。它没有 INI 的功能贫乏,没有 YAML 的过度工程,也没有 JSON 的注释缺失。
对于新项目,如果你要的是"人手写、机器解析、结构不太复杂"的应用配置,TOML 是 2026 年我会默认推荐的选择。这不是跟风,而是算过账的。