JSON1

语法

JSON 能写注释吗

不能。JSON 里没有注释语法,从来也没有过 —— 这个格式当初就是作为数据交换格式定的,而注释是人和人之间的说明,不是数据。答案就这么短,所以这页剩下的篇幅讲替代方案,以及哪些变通办法真能扛过构建流水线。

简短的答案

制定 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)
位置 17 就是第一个 /。消息里完全没提注释,所以通常的反应是回去找漏掉的逗号。

JSON5 和 JSONC

有两个超集,选之前得知道它们的区别。两个都不是 JSON:用任一种写的文件,JSON.parse 和所有严格解析器都会拒绝。

  • JSONC 是 JSON 加注释和尾随逗号。仅此而已。VS Code 的 settings.json 用的就是它,TypeScript 编译器在 tsconfig.json 里接受的也是它 —— 这就是那里能写注释的原因,也难怪有人由此推断 JSON 允许注释。
  • JSON5 走得远得多:键不用加引号、单引号、十六进制数字、小数点前后可以留空、InfinityNaN、多行字符串。它离 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
}
合法 JSON,也读得懂。但 _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"
  ]
}
整行的注释没了,这是预期之内 —— JSON 装不下它们。但你看 host

该怎么做

这个决定其实取决于文件是干什么的。在机器之间流动的数据不需要注释。人要编辑的文件需要,而那种文件本来就不该是 JSON。

  • API 数据: 不写注释,不用超集。把字段写在 schema 或 API 文档里,读的人真的会去那儿看。
  • 只被一个你掌控的工具读取的配置: 那个工具支持就用 JSONC,否则先剥离再解析。
  • 多人一起改的配置: 挪到 YAML 或 TOML,别跟格式硬扛。
  • 任何需要就地解释的东西: 如果这个解释值得写下来,它就值得放进 schema 的 description,那里工具真的能把它展示给读的人。