简短的答案
制定 JSON 的 Douglas Crockford 是刻意去掉注释的。他给出的理由是:有人开始往注释里塞解析指令,这会破坏互操作性 —— 而互操作性是这个格式存在的唯一理由。
所以 //、/* */ 和 # 全都是语法错误,在任何合规的解析器里,在任何语言里。没有开关能打开它们,因为语法里根本没有对应的产生式。
写了会怎样
报错里很少出现「注释」这个词,这就是为什么很多人会被绕住。解析器走到那个 /,而它期待的是逗号或右花括号,于是报的是后者。
| 写法 | 结果 |
|---|---|
// 说明 或 # 说明 | 语法错误 |
/* 说明 */ | 语法错误 |
{"port": 8080,} | 语法错误:尾随逗号 |
{'port': 8080} | 语法错误:单引号 |
{port: 8080} | 语法错误:键没加引号 |
输入
{
"port": 8080 // the port
}报错
Expected ',' or '}' after property value in JSON at position 17 (line 2 column 16)
/。消息里完全没提注释,所以通常的反应是回去找漏掉的逗号。JSON5 和 JSONC
有两个超集,选之前得知道它们的区别。两个都不是 JSON:用任一种写的文件,JSON.parse 和所有严格解析器都会拒绝。
- JSONC 是 JSON 加注释和尾随逗号。仅此而已。VS Code 的
settings.json用的就是它,TypeScript 编译器在tsconfig.json里接受的也是它 —— 这就是那里能写注释的原因,也难怪有人由此推断 JSON 允许注释。 - JSON5 走得远得多:键不用加引号、单引号、十六进制数字、小数点前后可以留空、
Infinity和NaN、多行字符串。它离 JavaScript 对象字面量比离 JSON 更近。 - 两者都没有注册的媒体类型。 不存在
application/json5,所以它们是给你自己掌控的工具用的文件格式,不是 API 的传输格式。
| 自己维护的配置文件 | API 请求或响应 | |
|---|---|---|
| JSON | 可以 | 可以 |
| JSONC | 读它的程序支持才行 | 不行 |
| JSON5 | 读它的程序支持才行 | 不行 |
四种变通办法
按可靠程度排序。前两个没问题;第三个有实际代价;第四个是个坑。
- 1. 换一个本来就有注释的格式。 如果这个文件是人在维护的配置,YAML 和 TOML 的语法里都有
#注释。这不是变通,这是正解 —— 配置文件没什么理由非得是 JSON。 - 2. 解析前先把注释剥掉。 JSONC 风格配置的标准做法:先跑一遍剥离,再
JSON.parse。用真正的 JSONC 解析器,别用正则,因为正则会毫不犹豫地毁掉字符串值里的//,比如一个 URL。 - 3. 加一个附带的键。 这是合法 JSON,而且当文件必须保持严格时,有时只能这样。代价是它现在是数据了:会发到客户端、会出现在解析后对象的 diff 里,而任何设了
additionalProperties: false的 schema 都会拒绝它。 - 4. 不要用加一个 `"_disabled"` 之类的键来「注释掉」一段。 那一段照样被解析、照样存在,而下一个人不会知道这个标记本来是要覆盖哪几个键的。删掉它,历史在版本控制里。
附带键的写法
{
"_comment": "port must match the load balancer",
"port": 8080
}_comment 现在是你数据里的一个字段,严格的 schema 会拒绝它。转成 YAML 来写注释
如果目的是要一份人能加说明的配置文件,那把 JSON 转成 YAML 一次、然后手写注释,是干净的路子。反方向才是会丢东西的地方,而这值得看清楚是怎么丢的。
- 整行的 `#` 注释被丢掉。 这是对的:JSON 里没地方放它们。
- 未加引号的标量后面跟的 `#` 会变成值的一部分。
host回来时是"localhost # trailing comment"。YAML 自己的规范要求行内注释前有个空格、并把它当注释处理,所以这是转换器的局限,不是 YAML 的。给值加上引号、或者把注释单独放一行,就能干净地转回来。 - 所以把这个转换当成单向的。 从 JSON 生成 YAML,在 YAML 里加说明,然后就以 YAML 为准,别来回转。
带注释的 YAML
# the port the server binds to port: 8080 host: localhost # trailing comment features: # experimental, off by default - search - export
转成 JSON
{
"port": 8080,
"host": "localhost # trailing comment",
"features": [
"search",
"export"
]
}host。该怎么做
这个决定其实取决于文件是干什么的。在机器之间流动的数据不需要注释。人要编辑的文件需要,而那种文件本来就不该是 JSON。
- API 数据: 不写注释,不用超集。把字段写在 schema 或 API 文档里,读的人真的会去那儿看。
- 只被一个你掌控的工具读取的配置: 那个工具支持就用 JSONC,否则先剥离再解析。
- 多人一起改的配置: 挪到 YAML 或 TOML,别跟格式硬扛。
- 任何需要就地解释的东西: 如果这个解释值得写下来,它就值得放进 schema 的
description,那里工具真的能把它展示给读的人。
去试试,或者继续读
- JSON 转 YAML转一次,然后写上 YAML 原生支持的注释。
- JSON 与 YAML 对比一份文件本来就该用哪个。
- JSON 转义规范强制、但同样让人意外的另一条规则。
- JSON 完全指南完整语法,按你需要的顺序讲。