不喜欢广告? 无广告 今天

YAML 与 JSON 与 TOML — 你应该真正使用哪种配置格式?

更新于

YAML、JSON 和 TOML 都用于存储配置,但它们是不可互换的。YAML 会静默地将国家代码转换为布尔值。JSON 不允许你添加注释。TOML 是团队中没人之前使用过的格式。以下是选择合适格式的方法。

YAML与JSON与TOML——你应该实际使用哪种配置格式?1
广告 移除?

2015年,一个Ansible的Playbook因为这一行而崩溃:

country: NO

配置加载时没有错误。没有解析器的抱怨。但 country 并未被设置为字符串 "NO"。它被设置为 false。因为在YAML 1.1中, NO 是布尔值。同样, yes, on, off, y,并且 n也是布尔值。这就是挪威问题,它多年来一直默默地破坏着配置文件。

这不是一个YAML的错误报告。这是一个引导你即将做出的决定:你的下一个配置文件使用YAML、JSON还是TOML?每种格式都有实际的权衡,而“只需使用生态系统中已有的格式”这一答案并不总是适用。

相同的配置,三种方式

在崩溃发生之前,以下是用这三种格式编写的相同应用程序配置:

YAML

# App configuration
app:
  name: my-api
  port: 8080
  debug: false

database:
  host: localhost
  port: 5432
  name: mydb
  pool_size: 10

logging:
  level: info
  format: json
  outputs:
    - stdout
    - /var/log/app.log

JSON

{
  "app": {
    "name": "my-api",
    "port": 8080,
    "debug": false
  },
  "database": {
    "host": "localhost",
    "port": 5432,
    "name": "mydb",
    "pool_size": 10
  },
  "logging": {
    "level": "info",
    "format": "json",
    "outputs": [
      "stdout",
      "/var/log/app.log"
    ]
  }
}

托米

# App configuration
[app]
name = "my-api"
port = 8080
debug = false

[database]
host = "localhost"
port = 5432
name = "mydb"
pool_size = 10

[logging]
level = "info"
format = "json"
outputs = ["stdout", "/var/log/app.log"]

YAML是最紧凑的,但语法负担最重。JSON是最冗长的,但最明确。TOML处于中间位置:无需YAML的隐式类型转换即可读取。

YAML:强大、宽容且充满陷阱

YAML是CI/CD流水线(GitHub Actions、GitLab CI、CircleCI)、Kubernetes清单、Ansible Playbook以及大多数开发工具的默认选择。你实际上并没有选择YAML——它是选择你的。

这些陷阱,已文档化:

1. 布尔值问题(挪威问题)

YAML 1.1——大多数解析器实际实现的规范——将大量字符串视为布尔值:

# YAML 1.1 boolean values (all parsed as true or false)
enabled: yes      # true
disabled: no      # false  
active: on        # true
paused: off       # false
valid: true       # true
invalid: false    # false

# The Norway Problem in practice:
country_codes:
  NO: Norway      # Key "NO" is fine, but value "NO" becomes false
  SE: Sweden
  YES: Yemen      # "YES" also becomes true

# The fix: quote your strings
country_codes:
  NO: "Norway"
  SE: "Sweden"

YAML 1.2(2009年发布)解决了这个问题——只有 truefalse 是布尔值。问题是PyYAML直到2021年的6.0版本才完全采用1.2的行为,而Go的流行库 gopkg.in/yaml.v2 截至2024年仍使用1.1的语义。如果你使用Ruby的Psych版本小于4.0或任何早于6.0的PyYAML,你实际上在使用1.1版本。

2. 制表符将杀死你的配置

YAML禁止使用制表符进行缩进。只有空格是有效的。你的编辑器可能在显示制表符和空格时看起来相同,配置文件可能看起来正确,但YAML仍会抛出错误:

yaml.scanner.ScannerError: while scanning a block mapping
  found character '\t' that cannot start any token

这是让初级开发者质疑自己职业选择的错误。请将你的编辑器配置为在YAML文件中将制表符扩展为空格。每个编辑器都支持此功能,但并非所有编辑器默认开启此功能。

3. 多行字符串并不直观

# | (literal block): preserves newlines exactly
description: |
  Line one.
  Line two.
  Line three.
# Result: "Line one.\nLine two.\nLine three.\n"

# > (folded block): folds newlines into spaces
short_desc: >
  This will all become
  one long line.
# Result: "This will all become one long line.\n"

# Trailing newlines: | adds one, |+ adds all, |- strips them all
desc_stripped: |-
  No trailing newline.

没有人能记住这一点而无需查阅资料。我使用的口诀是: | 看起来像换行符, > 看起来像被挤压在一起的内容。即使三年过去了,它仍然令人困惑。

当YAML胜出时

  • 你正在编写Kubernetes清单、GitHub Actions工作流或Ansible Playbook——你别无选择。
  • 你的配置包含大量解释非显而易见值的注释。YAML支持内联注释;JSON和TOML也支持,但YAML在注释密集的配置中感觉最为自然。
  • 你的数据具有深度嵌套的结构,而TOML的扁平表形式会显得非常糟糕。
  • 团队已经熟练掌握该格式,并且在流水线中已有校验工具(yamllint)。

JSON:枯燥但可靠的工具

JSON是为数据交换格式设计的,而不是配置格式。Douglas Crockford故意省略了注释——他的论点是,注释将被用于解析器会存在分歧的指令。因此 package.json 没有注释,而 tsconfig.json 是技术上带有注释的JSON(JSONC),大多数JSON解析器都不支持。

JSON在配置文件中的真正问题:

  • 没有注释。 你无法解释为什么 "maxRetries": 3 而不是5。你无法留下待办事项。你无法标记一个字段为已弃用。这对于生命周期超过作者的配置文件来说,确实非常痛苦。
  • 没有尾随逗号。 向数组添加项目意味着必须修改上一行以添加逗号。每个JSON的差异都变成两行更改。每个合并冲突都比需要的更糟糕。
  • 嵌套数据冗长。 六行的括号和方括号,而YAML只需三行缩进。
  • 所有数字类型相同。 JSON不区分整数和浮点数。 11.0 两者都是数字,而你的语言解析器将其解析为哪种类型取决于解析器本身。

但JSON的可预测性也是其主要特点。每个语言都有一个JSON解析器。规范明确无歧义。没有隐式类型转换。字符串始终是字符串—— "yes" 永远不会被静默地转换为 true。如果你需要在程序中验证JSON配置, IO Tools’ JSON 格式化器 可以在配置进入生产环境之前捕获语法错误——当有人手动编辑配置并忘记尾随逗号时,这非常有用。

当JSON胜出时

  • API响应或被多个语言/服务消费的配置。JSON是通用的;TOML在某些生态系统中的支持是零散的。
  • 你需要严格的类型保证。JSON Schema验证成熟、支持良好且被广泛使用(VS Code使用它来实现设置自动补全)。
  • 配置是机器生成的。没有人会手动编写JSON——但机器可以轻松生成它。
  • 你正在使用Node.js或前端JavaScript,其中JSON是原生公民。

TOML:为配置设计的有意见的格式

TOML(Tom的明显、最小语言)由GitHub联合创始人Tom Preston-Werner为配置文件专门创建。它于2021年1月达到v1.0版本。它是Rust的 Cargo.toml、Python的 pyproject.toml以及Hugo静态站点的默认格式。

TOML的设计理念:类型应明确,结构应尽可能扁平,且任何配置值应有唯一明显的方式书写。

# Types are unambiguous in TOML
name = "my-app"          # string: always quoted
port = 8080              # integer
threshold = 3.14         # float
enabled = true           # boolean: only true/false, no yes/no
created = 2024-01-15     # date: native type
tags = ["api", "prod"]   # array

# "yes" is just a string. Always.
country = "NO"           # string "NO", no boolean nonsense

粗糙的边缘:

  • 表数组语法确实笨拙。 [[products]][products.details] 看起来相似但行为完全不同。规范是合理的,但视觉上的区别并不明显。
  • 深度嵌套变得冗长。 YAML用5行缩进完成的事情,TOML需要3个独立的节标题。对于深度超过3层的配置,TOML开始显得是错误的工具。
  • 解析器的可用性。 TOML解析器存在于每个主要语言中,但它们在规范符合性上有所不同。 TOML符合性测试套件 定期揭示边缘情况。与之相比,JSON解析器的使用量高出数个数量级。
  • 团队熟悉程度。 如果你在非Rust或Python生态系统中使用TOML,预计至少有一名团队成员会提交一个“这是什么格式?”的PR。

当TOML胜出时

  • Rust项目—— Cargo.toml 是标准,工具链非常优秀。
  • Python项目使用 pyproject.toml (PEP 518)——这是目前推荐的工具配置位置,如Black、Ruff、mypy和pytest。
  • 简单扁平的配置,其中YAML的缩进敏感性会成为劣势。
  • 你希望拥有原生的日期/时间支持,而无需将其序列化为字符串。

快速决策指南

  • Kubernetes / CI/CD流水线 / Ansible? YAML。没有选择。
  • 被多个服务在不同语言中消费的API配置? JSON。
  • Rust项目? TOML(Cargo.toml约定)。
  • Python项目配置(linters、formatters、构建工具)? TOML(pyproject.toml是现在的标准)。
  • 静态站点配置(Hugo、Zola)? TOML,尽管这些通常支持所有三种格式。
  • Node.js项目配置? JSON(package.json生态系统),或如果你需要注释,则使用YAML。
  • 人类会频繁编辑,并需要留下备注? YAML或TOML(两者都支持注释)。不是JSON。
  • 你想要严格的类型安全和模式验证? JSON + JSON Schema。

大多数新项目诚实的答案:使用主要语言生态系统所期望的格式。Rust期望TOML。Python工具期望TOML或YAML。Node.js期望JSON。如果你编写的是语言无关的项目,TOML用于人类编辑的配置,JSON用于机器生成或消费的配置,是一个合理的默认划分。

想要享受无广告的体验吗? 立即无广告

安装我们的扩展

将 IO 工具添加到您最喜欢的浏览器,以便即时访问和更快地搜索

添加 Chrome 扩展程序 添加 边缘延伸 添加 Firefox 扩展 添加 Opera 扩展

记分板已到达!

记分板 是一种有趣的跟踪您游戏的方式,所有数据都存储在您的浏览器中。更多功能即将推出!

广告 移除?
广告 移除?
广告 移除?

新闻角 包含技术亮点

参与其中

帮助我们继续提供有价值的免费工具

给我买杯咖啡
广告 移除?