Skip to content
返回博客
教程

TOML vs JSON vs YAML:该选哪种配置格式?

对比 TOML、JSON、YAML 三种配置格式:注释、数据类型、日期、嵌套与真实陷阱。用清晰的决策矩阵选对格式,并附三者在线互转技巧。

13 分钟

TOML vs JSON vs YAML:该选哪种配置格式?

先给 TOML vs JSON vs YAML 一个简短结论:JSON 是每种语言都能解析的机器交换默认格式,YAML 是驱动 Kubernetes 和 CI 流水线的人性化格式,而 TOML 是为工具和应用配置量身打造的显式、强类型格式,像 Cargo.tomlpyproject.toml 就是它的地盘。没有哪种格式能处处胜出。选对配置文件格式,取决于三件事:谁来编辑它、你要不要注释、类型必须多严格。

一条快速经验法则。机器写、机器读,就用 JSON。有人每天手改深度嵌套的基础设施配置,YAML 的复杂度就值回票价。想要一眼看懂、不易出错的强类型配置,TOML 是最稳妥的选择。这三种格式其实算不上竞争对手,它们各自占着不同的车道:JSON 给机器,YAML 给 DevOps,TOML 给工具配置。并排示例、会引发真实 bug 的差异,再加一张决策矩阵,会逐条支撑这个判断。

30 秒速答

如果你只有半分钟,这张表就是你要找的配置文件格式对比。

JSONYAMLTOML
注释有(#有(#
数据类型显式、极简隐式(从形态推断)显式、丰富
原生日期取决于版本有(四种类型)
嵌套风格花括号 {}缩进[section]
缩进敏感是(禁用 tab)
多行字符串无(只能 \n有(|>有("""
尾逗号禁止不适用数组中允许
是 JSON 的超集是(1.2)
最适合API、数据交换K8s、CI、AnsibleRust/Python 工具配置

从这张表能得出三条一句话裁决:

  • 选 JSON:文件由程序产出、由程序消费时——API 载荷、package.jsontsconfig.json,任何跨越系统边界的数据。
  • 选 YAML:人要手改深度嵌套的配置,而且生态本身就默认它时——Kubernetes 清单、GitHub Actions、Docker Compose、Ansible playbook。
  • 选 TOML:你想给某个工具或应用一份清晰、强类型的配置,整体扁平只带几个段落,编辑者水平参差不齐时——Cargo.tomlpyproject.toml、Hugo、Netlify。

这些裁决为什么成立值得细看,因为真正的 bug 就藏在这些原因里。

同一份配置,三种写法

没有什么比把同一份配置写三遍更能快速说明差异。这里是一份小小的服务配置:一个名称和版本、两个顶层开关、一个功能列表,以及一个嵌套的数据库块。

JSON:

{
  "name": "acme-api",
  "version": "2.4.0",
  "debug": false,
  "released": "2026-07-02",
  "features": ["auth", "metrics", "tracing"],
  "database": {
    "host": "db.internal",
    "port": 5432,
    "max_connections": 100
  }
}

YAML:

name: acme-api
version: "2.4.0"
debug: false
released: "2026-07-02"
features:
  - auth
  - metrics
  - tracing
database:
  host: db.internal
  port: 5432
  max_connections: 100

TOML:

name = "acme-api"
version = "2.4.0"
debug = false
released = "2026-07-02"
features = ["auth", "metrics", "tracing"]

[database]
host = "db.internal"
port = 5432
max_connections = 100

三者描述的是完全相同的数据。看看你的目光会在哪里绊住。JSON 的标点最多:每个键都要双引号,每一层都加花括号,多出一个尾逗号就是语法错误。YAML 几乎把这些全部剥掉,结构完全来自缩进,读起来很漂亮,直到有个 tab 悄悄溜进来。TOML 居于两者之间:只有字符串要引号、key = value 成对出现,还有一个 [database] 头像老式 INI 文件那样为嵌套块命名。注意 released 字段在三份里都是加引号的字符串,好让数据在这里保持一致。这是刻意为之的,因为日期恰恰是这些格式开始各执一词的地方。

JSON —— 机器交换的默认格式

JSON 是你想都不用想就会伸手去拿的格式,而且这种直觉通常没错。每种主流语言都自带解析器,每个 HTTP API 都会说它,它的语法小到能装进脑子里。它严格且无歧义:字符串就是字符串,5432 就是数字,true 就是布尔值,而且每一种都只有唯一一种写法。正是这种严格,让 JSON 能安全地在素未谋面的系统之间传递。当一个 Node 服务把 JSON 发给一个 Go 服务时,两边对含义的理解是逐字节一致的。

同样是这份刚性,让 JSON 在自家地盘上称王。package.jsontsconfig.jsoncomposer.json 都是 JSON,因为工具会不断生成和改写它们,而机器写的配置需要一个机器级严格的格式。对于 API 载荷和消息队列,实在没有理由选别的。

JSON 的弱点,全都在有人要手动编辑它的那一刻暴露出来。没有注释。没有尾逗号,所以重排一个列表就得修逗号。没有多行字符串,只有 \n 转义。每个键都要双引号。还没有日期类型,所以 2026-07-02 只能作为加引号的字符串存活,再靠约定去解析。对于一份由人维护的配置文件,这些缺口叠加起来就是摩擦。

「JSON 没有注释」这个问题

被呼声最高的 JSON 特性,恰恰是 Douglas Crockford 故意留下的空缺:注释。他的理由是,注释会诱使人把解析指令偷偷塞进配置文件,从而破坏互操作性。无论你怎么看这个决定,你都没法给一份纯 JSON 配置写注解,这对任何由人维护的东西都很痛苦。实用的补救办法是用超集。JSON5 加入了注释、尾逗号、无引号键和单引号;JSONC(带注释的 JSON)是更轻量的变体,VS Code 用它来存设置。两者都保留了 JSON 的形态,同时让它可编辑。如果你需要注释、又想留在 JSON 家族里,读读我们的 JSON5 与 JSONC 格式化指南,看看各自何时该用、以及如何让工具链满意。

YAML —— 人性化的 DevOps 标准

YAML 为键盘前的那个人做了优化。它支持注释,基于缩进的布局让深度嵌套结构保持整洁,而锚点(anchor)让你把一个块定义一次、再用别名复用,而不必复制粘贴:

defaults: &defaults
  timeout: 30
  retries: 3

staging:
  <<: *defaults
  host: staging.internal

production:
  <<: *defaults
  host: prod.internal

&defaults 锚点给一个块命名,<<: *defaults 把它合并进两个环境,于是改一次共享的 timeout 只需在一处动手。JSON 和 TOML 都没有类似的东西。正是这种可读性,让 YAML 成了运维界的通用语:Kubernetes 清单、GitHub Actions 工作流、Docker Compose 文件、Ansible playbook 全都是 YAML。当你的配置嵌套五层深、而且有人每天调整它时,YAML 的低标点密度是实打实的解脱。

代价是复杂度和意外。YAML 1.2 规范很庞大,完全遵从的实现很少见,所以不同解析器在边缘情况上各执己见。缩进具有意义,tab 被直接禁用,一个错位的空格就可能改变含义或导致解析失败。最锋利的那道边是隐式类型:YAML 从一个无引号标量的形态去猜它的类型,而它猜错的频率高到有了个外号。

一段话讲清挪威问题

在 YAML 里写 country: NO,许多解析器会给你布尔值 false 而不是字符串 "NO",因为 YAML 1.1(大多数 Kubernetes 工具链仍在遵循它)把 NOYESONOFFYN 当作布尔值。挪威的国家代码就这么无声无息地变成了 false,没有任何报错。同样的隐式类型把 0755 变成八进制数、把 1.20 变成 1.2。补救办法很无聊但很可靠:给那些可能被误认成别的东西的字符串加上引号。这个坑有名到值得单独深挖一次,包括真实的线上事故以及 YAML 1.1 与 1.2 的历史渊源,都在我们的 YAML 挪威问题指南里。对本文来说,结论很简单:YAML 的便利和它的危险来自同一个特性。

当人每天编辑深度嵌套的配置、而周围生态本就要求用它时,YAML 胜出。如果你在写 Helm chart,那你就是在写 YAML,这没问题。

TOML —— 为工具打造的显式配置

TOML(Tom’s Obvious, Minimal Language,由 Tom Preston-Werner 创建)被设计成 YAML 用户梦寐以求的那种配置格式:可读,但不用猜。它的定义性特征是显式。类型毫无歧义,所以 port = 5432 永远是整数,name = "acme" 永远是字符串,没有基于形态的推断能把你绊倒。TOML 语法从 INI 文件借来了 [section] 头,让配置的顶层一扫即懂;它不缩进敏感,所以空白永远不会改变含义。TOML 1.0.0 规范小而稳定,这本身就是一个优点:能记错的地方更少。

TOML 的招牌能力是原生的日期与时间类型,而 JSON 和纯 YAML 都无法干净利落地处理它们。它有四种:带时区的偏移日期时间(1979-05-27T07:32:00Z)、不带时区的本地日期时间、本地日期(1979-05-27,就是一个日历日)、以及本地时间(07:32:00)。这意味着一个部署日期会保持为真正的日期,而不是一个你还得重新解析的字符串。

三块 TOML 语法承担了大部分工作。一个 [section] 头打开一张表(table);像 [tool.ruff] 这样的点分头把一张表嵌进另一张表里而无需额外缩进;而一个内联表 { version = "1.0", features = ["derive"] } 把一个小对象打包进一行。重复的段落用双重头来表示,也就是表数组(array of tables):

[[servers]]
name = "alpha"
ip = "10.0.0.1"

[[servers]]
name = "beta"
ip = "10.0.0.2"

这个块会变成键 servers 下由两个对象组成的 JSON 数组。它对扁平列表读起来很顺,而这恰恰是大多数工具配置所需要的。

它的弱点真实存在,但很局限。TOML 没有 null 类型,所以一个缺失的值就是一个被省略的键。深度嵌套会变得冗长,因为重复嵌套结构的每一层都要把完整的 [[path]] 头再写一遍,噪声比对应的 YAML 更大。而且 TOML 比 JSON 或 YAML 更年轻、更不普及,所以不是每个工具都会说它。TOML 最适合整体扁平、只带少数几个带标签段落的配置,而这描述了绝大多数工具配置。

为什么 Rust 和 Python 选 TOML?

TOML 的崛起跟随了两个把它当作标准来采用的生态。Rust 的包管理器 Cargo 为每个 crate 使用 Cargo.toml,所以每个 Rust 开发者从第一天起就在读写 TOML。Python 紧随其后:PEP 518 引入了 pyproject.toml 来声明构建需求,PEP 621 又在同一个文件里标准化了项目元数据(名称、版本、依赖以及 [tool.*] 段落),取代了散落的 setup.pysetup.cfg 和各工具各自的配置。除这两者之外,Hugo、Netlify、Poetry 和 Foundry 也全都默认用 TOML。共同的主线是:贡献者手动编辑的工具配置,从「显而易见」而非「花哨」中受益。

正面交锋 —— 那些真正咬人的差异

特性表格很整洁。bug 可不整洁。以下是那些会引发真实问题的具体差异,每一个都配了一个你能扔进任何解析器跑一遍的小例子。

注释

JSON 根本没有注释语法。TOML 和 YAML 都用 # 一直到行尾。

# TOML: a leading comment
port = 5432  # and a trailing one
# YAML: identical comment style
port: 5432   # trailing comment

你一把 //# 粘进严格的 JSON,解析就会失败。这不是外观问题。一份人在没有注释的情况下维护的配置,会积累起只活在某个人脑子里的部落知识。

数据类型与日期

JSON 的类型显式但稀少,没有日期类型。TOML 的类型显式而丰富。YAML 的类型是隐式的,而且如前所述,偶尔还是错的。日期是最清晰的分水岭。TOML 原生表达全部四种:

odt = 1979-05-27T07:32:00Z   # offset date-time (has timezone)
ldt = 1979-05-27T07:32:00    # local date-time (no timezone)
ld  = 1979-05-27             # local date (a calendar day)
lt  = 07:32:00               # local time

把这段 TOML 转成 JSON,每个值都会变成一个保留了其含义的字符串:一个本地日期会保持为 "1979-05-27",而不是被膨胀成一个假的午夜时间戳。我们的 TOML 转 JSON 工具会精确保留这个日期种类,而许多幼稚的转换器都会搞错。与此同时,YAML 到底会不会把 1979-05-27 当作日期,取决于解析器和 schema 版本,而这正是你在生产配置里最不想要的那种歧义。

嵌套深度

配置嵌套得越深,各格式就分歧越大。拿一棵小小的 Docker-Compose 风格的树来说:

services:
  web:
    build:
      context: .
      args:
        NODE_ENV: production
    ports:
      - "8080:8080"

YAML 用纯缩进表达深度,而且保持紧凑。同样的结构在 TOML 里逼着你在每个头里拼出完整路径,还要把标量值挪到子表前面:

[services.web]
ports = ["8080:8080"]

[services.web.build]
context = "."

[services.web.build.args]
NODE_ENV = "production"

JSON 用嵌套花括号承载这棵树,无歧义但标点密集。对浅层配置,三者感觉相近;一旦过了三四层,YAML 明显更精简,TOML 明显更重复。就这一个差异,解释了为什么 Kubernetes 选了 YAML 而 Cargo 选了 TOML。

缩进敏感

只有 YAML 在乎空白。在 JSON 和 TOML 里,你想怎么缩进就怎么缩进,甚至完全不缩进,含义都一样。在 YAML 里,缩进就是结构,而 tab 是非法的,所以一个插入了 tab 的编辑器、或者一个粘贴到错误深度的块,会无声地改变文档或拒绝加载。一个多缩进了一个空格的列表项,可能在没有任何报错的情况下挂到错误的父键上,而这是一种用肉眼极难发现的抓狂 bug,因为文件看上去仍然合理。如果你的贡献者用着五花八门的编辑器和设置,那就是 YAML 一直在征收、而另外两种不收的一笔税,也是在任何大型 YAML 配置的 CI 里跑一个 linter 的有力理由。

多行字符串

JSON 无法在字符串里放一个字面换行符;你得把它转义成 \n。YAML 和 TOML 都有真正的多行字符串。

{ "note": "line one\nline two" }
note: |
  line one
  line two
note = """
line one
line two
"""

YAML 的 | 保留换行(而 > 块会把换行折叠成空格),TOML 的 """ 表现类似,会裁掉紧跟在开头引号之后的那个换行。对于内嵌脚本、SQL 或帮助文本,光这一点就能定下格式。

尾逗号与严格性

JSON 是三者中最严格的。最后一个数组元素后面的一个尾逗号就是语法错误:

# valid TOML — the trailing comma is fine
ports = [
  8001,
  8002,
]

同样是 8002 后面的尾逗号,在 JSON 里就解析失败,这就是为什么给 JSON 数组加一行往往还意味着要修上一行的逗号。TOML 允许尾逗号,而 YAML 每行一个短横的列表语法干脆绕开了这个问题。严格性对机器交换是美德,对手动编辑是烦恼,这是同一个权衡从另一面看的样子。这也正是 JSON5 和 JSONC 专门要为 JSON 消除的那份摩擦。

大整数与 2^53 天花板

三种格式都能把一个 64 位整数成文本。麻烦从这个值经过 JavaScript 时才开始,因为浏览器的 JSON 数字是 IEEE-754 双精度浮点数,只能精确表示到 2^53 − 1(9007199254740991)为止的整数。一个 Snowflake ID 或者一个纳秒时间戳比这更大,所以经过一个基于 JS 的工具往返一趟就会把它四舍五入、破坏这个值:

snowflake = 1420070400000000000   # exact in TOML text
{ "snowflake": "1420070400000000000" }

在每种格式里都可靠的补救办法,是把这类值存成加引号的字符串,这样任何数值解析器都碰不到它们。这不是哪一种格式的缺陷;它是执行转换的运行时的一个局限。我们的转换器会在一个整数越过那条线时警告你,而不是悄悄地把它弄坏。

如何选择 —— 决策矩阵与流程图

当你真要决定用哪种配置格式时,按顺序走一遍这些问题,在第一个强烈的「是」处停下。

  1. 这个文件主要由程序写、由程序读,或者生态已经强制了某种格式吗? 如果是,用 JSON。API 载荷是 JSON。package.json 旁边的文件是 JSON。别跟工具链对着干。
  2. 人在一个本就期望它的技术栈里(Kubernetes、CI、Ansible),每天手改深度嵌套的配置吗? 如果是,用 YAML,并采用一套加引号的约定来躲开挪威问题。
  3. 这是工具或应用配置,整体扁平只带几个段落,由水平参差的贡献者编辑,而且「有类型、显而易见」胜过「简短」吗? 如果是,用 TOML

如果两个答案打平,就用这张轴向图来打破平局:

需求最佳选择原因
文件里要注释TOML 或 YAMLJSON 没有
原生日期/时间值TOML四种显式日期类型
极深的嵌套YAML缩进保持紧凑
零空白意外JSON 或 TOML不缩进敏感
锁定在某个生态文件里JSONpackage.jsontsconfig.json
人来编辑、段落不多TOML显式类型、类 INI 的头
机器到机器交换JSON通用、严格、快
类型推断绝不能靠猜JSON 或 TOMLYAML 隐式推断

大多数项目最后会用不止一种。一个仓库里可能有一份从不手动碰的 JSON package.json、一份给 Python 工具链的 TOML pyproject.toml.github 里的 YAML 工作流,还有一个本地密钥用的、这几种语法一个都不遵循的 .env 文件。这种混搭是常态,也正是要点所在:每个文件都待在适合其编辑者的格式里。如果 .env 这一层是你配置的一部分,我们的 .env 文件格式指南讲了它在 JSON 配置旁边处于什么位置、以及如何在两者之间转换。

在 TOML、JSON 和 YAML 之间转换

你在这些格式之间转换的频率,会比你想象的更高。一个 CI 任务读取手写的 pyproject.toml,需要把它变成 JSON 来喂给一个依赖看板。一次迁移把一个应用从 YAML 配置搬到 TOML 以获得更严格的类型。一次代码评审,当你 diff 两个文件的 JSON 形态、而不是它们对空白敏感的原文时,会更轻松。每个方向都有一个值得在粘贴前先知道的坑。

TOML 转 JSON。 主要风险在日期。一个像 1979-05-27 这样的本地日期应当保持为日历日期,而不是变成一个凭空造出时间和时区的午夜 UTC 时间戳。注释也会被丢掉,因为 JSON 装不下它们。TOML 转 JSON 工具会忠实保留每一种日期种类,让往返保持无损。

JSON 转 TOML。 TOML 对结构更严格,所以有两件事可能挡住转换。顶层必须是一个对象,因为一个 TOML 文档在其根部永远是一张表;一个裸数组或标量无处安放。而且 TOML 没有 null,所以一个 null 对象值会被丢掉(并附一条列出哪些键的警告),而一个数组内部的 null 根本没有合法的 TOML 表示。JSON 转 TOML 工具会把这两种情况都显式暴露出来,而不是产出坏掉的输出。

JSON 与 YAML,双向。 这里要盯的是挪威问题:一个无引号的 NO0755 在跨越边界时可能翻转类型。JSON 转 YAML 工具YAML 转 JSON 工具会处理好加引号,让字符串保持为字符串。

任何一次转换之后,都值得校验一下结果。把输出跑一遍 JSON 格式化工具,能在你提交或下发之前确认 JSON 格式良好、缩进一致。而且因为配置文件承载着密钥(仓库令牌、数据库凭据、部署密钥),所有这些都完全在你的浏览器里运行:不上传任何东西,所以一份带私有仓库令牌的 Cargo.toml、或者一份带凭据的 values 文件,永远不会离开你的机器。

FAQ

TOML 比 YAML 更好吗?

没有哪一个是普遍更好的;TOML vs YAML 的选择归结到配置的形态。TOML 靠显式类型和没有缩进陷阱,更难写错,所以它在整体扁平的工具配置上胜出。YAML 更紧凑地表达深度嵌套,所以它在像 Kubernetes 那样的大型分层基础设施上胜出。

配置文件该用 JSON 还是 YAML?

对于人来编辑的配置,优先选 YAML:它允许注释、读起来清爽,而这些 JSON 都做不到。对于程序生成和消费的配置,优先选 JSON:它严格、快、通用。分界线在于谁来编辑,是人还是机器。

为什么 JSON 不允许注释?

JSON 的创造者 Douglas Crockford 是故意移除注释的。他担心人们会在注释里嵌入解析指令,从而破坏解析器之间的互操作性。如果你需要在一个 JSON 形态的文件里写注释,用 JSON5 或 JSONC,它们在不改变核心结构的前提下把注释加了回来。

YAML 里的挪威问题是什么?

它是 YAML 1.1 把某些无引号的词当作布尔值,于是 country: NO 变成了 false 而不是字符串 "NO"YESONOFF 也是同样的行为。给值加引号能避免它;完整的来龙去脉见上文链接的专门指南。

TOML 支持注释吗?

支持。TOML 用 # 表示注释,既可以单独一行,也可以尾随一个值,和 YAML 完全一样。注意,当你把 TOML 转成 JSON 时注释会丢失,因为 JSON 没有注释语法来承载它们。

TOML 能表达 JSON 能表达的一切吗?

几乎能。JSON vs TOML 的差距很小但真实存在:TOML 没有 null 类型,所以 JSON 的 null 在转换时会被丢掉或拒绝;而且一个 TOML 文档在顶层必须是一张表,所以一个裸的 JSON 数组或标量必须先被包进一个键下面才能转换。

YAML 是 JSON 的超集吗?

从 YAML 1.2 起,是的:任何合法的 JSON 文档也是合法的 YAML,所以一个 YAML 解析器能直接读 JSON。这是个有用的事实,但别把它当成一条安全边界来依赖,因为 YAML 解析器加入了 JSON 没有的特性(比如锚点)。

哪种配置格式解析最快?

JSON 通常最快,在每种语言里都有最成熟、最优化的解析器。YAML 因为规范庞大和隐式类型,是最慢、解析起来最复杂的。TOML 居于中间,在速度上更接近 JSON 而非 YAML。

标签: toml json yaml configuration data-conversion