JSON、TOML、YAML、CSON:配置文件格式怎么选
结论:配置文件需要注释、日期与多行文本,这些恰好是 JSON 的短板。本文把 JSON、TOML、YAML、CSON 放进同一张表比较语法与工具支持,给出按场景的选型结论,并说明转换时哪些信息一定会丢。

结论:机器交换一律用 JSON,人手维护的配置按生态选 TOML 或 YAML(Kubernetes、CI 锁 YAML,结构扁平或要原生日期选 TOML);只有拿不准读取方是否支持方言时,才退回 JSONC。
JSON 是给机器交换数据用的,配置文件是给人写给人看的。把这两件事混为一谈,就会得到一份既不能写注释、又不敢删字段的
config.json。
一、先分清两类文件
数据交换格式和配置格式的目标并不相同,只是经常被同一个人写在同一台机器上。
| 维度 | 数据交换 | 配置文件 |
|---|---|---|
| 谁写 | 程序序列化 | 人手写或半手工维护 |
| 谁读 | 对方的程序 | 自家程序 |
| 关心什么 | 零歧义、体积、解析速度 | 可读性、可注释、可 diff |
| 变更频率 | 每次请求 | 偶尔,且要评审 |
| 出错代价 | 接口 500 | 服务起不来 |
JSON 在左列几乎满分:语法极简、所有语言都有解析器、没有注释也就没有「注释与值不一致」的问题。但它的严格性在右列全是负担:不能写注释、不能写尾逗号、不能写日期、多行字符串必须压成一行加 \n。
二、四位选手与语法能力
- JSON:RFC 8259 与 ECMA-404 定义,只有对象、数组、字符串、数字、布尔、
null六种值。它是数据交换的事实标准,也是唯一一个「不用选、默认就用它」的格式。 - TOML:Tom Preston-Werner 发起,v1.0.0 于 2021 年 1 月发布。定位很明确——「语义显而易见、能无歧义映射到哈希表」的最小配置格式。
Cargo.toml、pyproject.toml都以它为默认。 - YAML:1.2.2 是当前规范。缩进表达层级,支持注释、锚点、多文档。Kubernetes 清单、GitHub Actions、Ansible 都离不开它,代价是规范庞大、实现差异多。
- CSON:CoffeeScript Object Notation,用 CoffeeScript 的对象字面量语法写配置,Atom 编辑器的
config.cson就是它。注释、无引号键名、尾逗号都支持,但生态基本停留在 JavaScript 圈。
2.1 语法能力对照表
| 能力 | JSON | TOML | YAML | CSON |
|---|---|---|---|---|
| 注释 | ✗ | ✓ # |
✓ # |
✓ # |
| 尾逗号 | ✗ | ✓(数组、内联表) | ✓(流式集合) | ✓ |
| 键名可省略引号 | ✗ | ✓ | ✓ | ✓ |
| 原生日期时间 | ✗(只能字符串) | ✓ 四种日期时间类型 | ✓(1.1 自动解析) | ✗ |
| 多行字符串 | ✗(\n 转义) |
✓ """ 与 ''' |
✓ 块标量与折叠标量 | ✓ heredoc |
| 整数范围 | 规范不限,互操作按双精度 | 64 位有符号,必须无损 | 任意精度 + .inf/.nan |
跟随 JavaScript |
| 嵌套表达 | 花括号与方括号 | 表、数组表、点号键 | 缩进 | 缩进 |
| 缩进敏感 | ✗ | ✗ | ✓ | ✓ |
null |
✓ | ✗ | ✓ | ✓ |
| 工具覆盖 | 全部语言 | 主流语言 | 全部语言,实现有差异 | 仅 JavaScript 生态 |
这张表里有两格值得单独说:TOML 没有 null,这是从 JSON 转过去时最常见的失败点;YAML 的「原生日期」在 1.1 解析器里会自动把 2026-10-31 变成日期对象,跨语言传输时类型会漂移。
三、注释:JSON 唯一绕不过去的短板
先给结论:如果这份文件由人维护、需要解释「为什么是这个值」,就不该用标准 JSON。
现成的四条路,按侵入性从低到高:
- 换成 JSONC。标准 JSON 加注释与尾逗号,VS Code 的
settings.json、tsconfig.json都是它。代价是读取方必须用jsonc-parser一类带注释能力的解析器,用JSON.parse直接读会失败。细节见 JSON5 与 JSONC:带注释的 JSON。 - 写旁注文件。
config.json保持机器可读,config.md写清楚每个字段的含义与取值理由,两者在同一个目录、同一次提交里更新。 - 把说明塞进 Schema。JSON Schema 的
description、$comment关键字可以承载字段说明,但 Schema 通常不随运行时配置一起下发,读配置的人未必看得到。 - 换成 TOML 或 YAML。把注释能力交给格式本身,解析器天然支持。
{
// 固定 8080:前端构建脚本会读取这个端口做代理
"port": 8080,
// 关闭热更新,避免 CI 上文件监听耗尽 inode
"watch": false
}
JSONC 的问题在于它仍是「方言」:文件后缀还是 .json,但只有部分工具认。对外传输、跨团队交付时,它和标准 JSON 的边界必须写进文档。
四、日期与多行字符串:TOML 的主场
TOML 把日期时间提升为一等类型,分四种:带偏移的日期时间、本地日期时间、本地日期、本地时间。多行字符串也有专门的语法,换行原样保留:
title = "json-toolkit"
created = 2026-10-31T09:00:00+08:00
description = """
面向开发者的 JSON 工具集,
所有处理都在浏览器本地完成。
"""
同样的信息写成 JSON,日期只能退化成字符串,多行文本必须转义:
{
"title": "json-toolkit",
"created": "2026-10-31T09:00:00+08:00",
"description": "面向开发者的 JSON 工具集,\n所有处理都在浏览器本地完成。"
}
JSON 里 "created" 只是一个长得像日期的字符串,类型信息由字段契约承担:双方得额外约定「这个字段一律 RFC 3339」。TOML 则由解析器直接给出日期对象,不需要口头约定。
YAML 也能写多行文本,但两种写法语义不同:| 保留换行,> 折叠成空格。选错符号,一段地址或 SQL 就会在解析后变成一行,这种错误很难在 review 里看出来。
五、数字:最容易被忽略的差异
JSON 规范没有限制数字精度,但 RFC 8259 同时提醒:跨实现交换时按 IEEE 754 双精度处理才安全。JavaScript 里超过 2^53 - 1 的整数会静默丢精度:
JSON.parse('{"id":9007199254740993}').id
// 9007199254740992,最后一位被吃掉
TOML 明确要求 64 位有符号整数必须无损处理,超出范围要报错而不是悄悄截断;浮点按 binary64,另外支持 inf 与 nan。YAML 1.1 还多一个历史包袱:1:30 会被解析成 90(六十进制),布尔值接受 yes、no、on、off,于是经典的「挪威问题」——国家代码 NO 变成 false。
处理金额时,无论用哪种格式,都不要依赖浮点数:用整数分、字符串,或数据库的 decimal 类型。格式化与精度问题在 JSON 与 YAML、XML 的互转边界 里有更完整的讨论。
六、表结构与嵌套:TOML 的边界很清楚
TOML 用表表达嵌套,用双中括号表达数组表:
[server]
port = 8080
[[routes]]
path = "/parse"
method = "POST"
[[routes]]
path = "/minify"
method = "POST"
它有两个硬边界,转换前必须知道:
- TOML 文档的根是一个表,所以顶层是数组的 JSON(如
[1, 2, 3])没有合法的 TOML 表示,只能包一层{"items": [...]}; - TOML 没有
null,JSON 里的null字段要么删除、要么换成空字符串或哨兵值,语义由调用方承担。
YAML 则相反,嵌套深度不受限制,还能用锚点和合并键复用片段:
defaults: &defaults
retries: 3
timeoutMs: 5000
server:
<<: *defaults
port: 8080
锚点是 YAML 的独有能力,也是转换时的损失点:JSON 没有引用语法,展开后文件会变长,且再也认不出「这两段本来是同一条配置」。
七、按场景选型的结论
| 场景 | 推荐 | 理由 |
|---|---|---|
| 对外 API、消息队列、缓存 | JSON | 通用性最高,没有任何协商成本 |
| 应用配置,结构扁平、类型明确 | TOML | 有注释、有日期、整数无损,解析器行为稳定 |
| Kubernetes、CI、Ansible | YAML | 生态锁定,换格式等于换工具链 |
VS Code、tsconfig.json |
JSONC | 工具原生支持,无需引入新格式 |
| 需要复用配置片段 | YAML | 锚点与合并键是唯一现成方案 |
| 程序生成、只被程序读 | JSON(压缩) | 无需注释,体积与解析都最优 |
| 历史 Atom / CoffeeScript 项目 | CSON,但建议迁移 | 生态已边缘化,迁移成本随年限上升 |
一条经验:配置文件跟着生态走,不要跟着个人偏好走。 用 Rust 就写 Cargo.toml,部署到 Kubernetes 就写 YAML,做 VS Code 插件就写 JSONC;只有在没有生态约束的新项目里,才值得讨论「哪种更好写」。
八、转换边界:转过去往往回不来
| 转换方向 | 一定丢失的信息 | 建议 |
|---|---|---|
| JSON → TOML | null、顶层数组、无法表达的键名 |
先规整结构,剔空字段 |
| TOML → JSON | 注释、日期类型、inf / nan |
日期统一转 RFC 3339 字符串 |
| YAML → JSON | 注释、锚点与合并键、标签、多文档 | 先展开锚点,再逐文档转换 |
| JSON → YAML | 无结构性损失,但日期字符串可能被自动解析 | 给日期字符串加引号 |
| CSON → JSON | 注释、CoffeeScript 表达式 | 只做一次性迁移,不双向同步 |
转换工具能保证「值对」,但保证不了「注释还在」。所以不要设计「JSON 是源文件、YAML 是构建产物」这种双向流程;要么单向生成,要么只维护一份源文件。
九、实践清单
- [ ] 团队文档里写明「哪类文件用哪种格式」,并列出允许的方言;
- [ ] 人写的配置优先 TOML 或 YAML,机器交换一律 JSON;
- [ ] 日期字段在 JSON 里统一用 RFC 3339 字符串,并在 Schema 里标注格式;
- [ ] 金额与超过
2^53的整数用字符串或整数分,不依赖浮点; - [ ] CI 里对每种格式分别校验,不要用
JSON.parse校验 YAML; - [ ] 格式迁移只做单向,迁移后删除旧文件,避免两份配置漂移。
JSON 配置文件怎么加注释?
标准 JSON 不支持注释。可选方案有三种:改用 JSONC(VS Code 生态的方言,需要 jsonc-parser 读取)、维护一份旁注文档说明字段含义、或直接换成 TOML / YAML。对外传输的文件仍建议保持标准 JSON,把说明放在文档或 Schema 里。
TOML 和 YAML 哪个更适合应用配置?
结构扁平、以键值为主、需要日期类型时选 TOML,它的语义更确定,解析器差异更小。需要复杂嵌套、多文档、锚点复用,或者目标生态本身就是 YAML(Kubernetes、CI)时选 YAML。两者都能写注释,真正的区分点是结构复杂度与生态绑定。
为什么 YAML 里 NO 会变成 false?
YAML 1.1 把 yes、no、on、off、y、n 都当作布尔值,所以挪威的国家代码 NO 会被解析成 false。这是「挪威问题」。规避方法有两种:升级到按 YAML 1.2 解析的实现,或给所有可能被误判的字符串加上引号。
CSON 还值得用吗?
新项目不建议。CSON 依赖 CoffeeScript 生态,现成工具少,编辑器与 CI 支持都有限,Atom 停止维护后使用场景进一步收缩。如果项目里已有 CSON 配置,优先安排一次性迁移到 JSON、TOML 或 YAML,而不是继续新增。