JSON 配置文件是个好东西,绝大多数现代开发者每天都在和它打交道。但有一个问题,从入行第一天起就让人心里长刺:JSON 不支持注释,也不允许尾随逗号。你小心翼翼地在配置里加了一行说明,啪,解析器直接给你抛个红牌。你要在数组最后一项后面多加个逗号方便以后追加,啪,又是一声报错。这个问题在配置文件场景中尤其要命,因为配置的本质就是给人写的,需要解释、需要灵活。写代码还能加注释,配置文件反而要裸奔,这本身就有点反直觉。
这篇文章我打算从根上讲透这件事:为什么 JSON 会有这两个“反人类”设定,我们又是怎么在实践中绕过它的,以及今天有哪些成熟的解决方案——从 IDE 生态里的 JSONC,到更完整的超集 JSON5、HOCON,再到干脆换赛道的 YAML。我会给出不同技术栈下的接入方式、参数选型和踩坑记录,尽量让不管你是写前端、写 Python、写 Java 还是搞运维脚本的人,都能找到一套能直接抄作业的做法。
1. 为什么 JSON 在配置文件场景中天生吃亏
1.1 从 RFC 8259 说起:JSON 的设计初衷就不是给人手写的
JSON 的规范在 RFC 8259 里写得清清楚楚,它最初的设计目标是“语言无关的数据交换格式”。它的父亲 Douglas Crockford 在推广时强调的思路是:机器跟机器之间传递数据,要简单、稳定、无歧义。这种定位决定了它必须把语法压缩到极致,尽量减少“废话”。
注释是什么?注释是给人看的,对机器来说完全多余。尾随逗号是什么?是为了让人在长期维护时少删一行、多留一个位置,对机器来说只会增加解析负担和出错面。所以 JSON 干脆一刀切,这俩全不要。如果把 JSON 比作一段只有骨头的对话,那它就是那种只报“姓名、年龄、身份证号”的表格,能识别,但毫无温度。数据交换场景下这没问题,但一旦把 JSON 当配置文件用,问题就暴露了。
配置文件本质上是“代码和数据的交界地带”,它既要被程序解析,又要被人反复编辑。一个人员流动频繁的项目里,配置文件里的某个大数字、某个 URL 常量,如果没有注释说明来历,三个月后你自己看着都发懵。我见过太多团队在 JSON 配置里硬塞说明字段,比如 "_comment": "这个值不要随便改"。这种别扭的写法不仅污染数据,还会被程序当成真实字段读到,属于典型的狼狈妥协。
1.2 配置文件场景下的真实需求:注释和尾随逗号到底解决什么问题
我们来拆一下这两个需求在配置场景里的意义。
注释解决的是“传承”问题。举个例子,一个 Spring Boot 的 application.json,里面有几十个配置项,其中某个超时时间 "timeout": 30000,为什么是 30000?有没有人知道这是为了配合上游某个 SLA?如果是 JSON,要在别处维护一份 Markdown 文档来解释,配置和解释分离,最终一定有一边会过期。但如果可以在 JSON 里写 // 上游 SLA 要求 P99 不能超过 30s,这个值配合重试策略使用,信息就会跟着配置走,随代码版本一起更新,这才是配置文件该有的形态。
尾随逗号解决的是“编辑体验”问题。比如你维护一个白名单数组,里面列了二十个域名,某次要新增一个,传统 JSON 里你要先找到最后一个,把它的结尾逗号删掉,再换行写上新的,最后补一个逗号。整个操作三步里有两步是冗余劳动,而且还容易出错——手抖一下,最后一个值后面残留逗号,整个文件直接失效。如果支持尾随逗号,你只需要在末尾追加新行,原有的那些行一个都不用碰。这在大厂多人协作的代码评审里尤其重要,因为你每一处改动都可能引发冲突,减少无关 diff 就是对团队负责。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 主流方案盘点:JSONC、JSON5、YAML、HOCON 到底怎么选
2.1 JSONC:把注释加回去,但仅此而已
JSONC(JSON with Comments)是最朴素的改良方案,核心就是允许在 JSON 中使用 // 和 /* */ 两种注释语法。它并没有修改 JSON 的本体结构,只是在解析时先把注释剥掉,再走传统 JSON 解析。
这个方案最大的价值在于“兼容性缓冲”。很多工具其实已经在私下里支持 JSONC 了,最典型的就是 VS Code。VS Code 的配置文件 settings.json,看起来是 JSON 格式,实际上里面可以写注释。官方引入 JSONC 的原因很实际:不改变现有 JSON 文件的生态地位,去兼容那些已经写了注释的用户文件。
但 JSONC 仍然不允许尾随逗号。究其原因,是尾随逗号的支持需要改动真正的语法解析逻辑,而不仅仅是过滤注释。JSONC 说白了只是一个“预处理器”级别的小打小闹,对尾随逗号这个痛点并无帮助。所以如果你的烦恼主要是“我想在配置里写注释”,JSONC 够用;如果还想舒服地编辑数组成员,那得继续往下看。
2.2 JSON5:超集方案,一次补齐两个痛点
JSON5 是真正的“JSON 超集”,由 Aseem Kishore 在 2012 年左右发起,设计目标就是“让 JSON 更像 JavaScript 对象字面量”。它一次性解决了多个 JSON 的痛点:
- 支持注释,包括单行
//和多行/* */ - 支持尾随逗号,对象和数组都可以
- 支持键名不带引号,比如
{ name: "foo" } - 支持单引号字符串
- 支持十六进制数字写法、
+Infinity等特殊值 - 字符串可以跨行
换句话说,JSON5 是所有 JSON 超集方案里最“完整”的一个。它的解析规则基本对齐了 JavaScript 的对象字面量语法,前端开发者拿到手里几乎零学习成本。
JSON5 有现代化的解析器实现,比如 json5 这个 npm 包,JavaScript 生态里用得极广。它在 Node.js 环境下解析速度不错,而且对错误提示做了专门优化,报错时会告诉你“哪个位置、哪个 token、为什么失败”,从排查问题角度说,体验比原生 JSON.parse 好太多——原生 JSON.parse 最爱在 Unexpected token 之后给你一个谁都能猜到的行号,却不告诉你上下文。
2.3 YAML:换个赛道,大杀器但不是没有代价
YAML 可能是配置文件领域风头最劲的格式。Kubernetes、Ansible、GitHub Actions、Docker Compose,这些基础设施级工具清一色选 YAML,原因很简单:YAML 天然支持注释,同时完全没有逗号这种“噪声”。一个数据序列,换行缩进就能表达层级,末尾不需要任何分隔符。
但这种“隐形语法”也是一把双刃剑。YAML 的痛点是它的规范极其复杂,光常见“陷阱”就能拉出长长一张清单:纯量歧义、缩进行为不一致、特殊字符串解析、锚点和别名副作用……尤其当你用 YAML 描述复杂嵌套结构时,空格多一个少一个都可能让整个文件语义悄悄改变。这不是危言耸听,我用 Hexo 写博客时就吃过亏,某个字段只有一层缩进不对,生成结果直接乱掉,日志还屁都不报。
所以 YAML 是配置场景的“优秀选项”,但并非“零成本选项”。如果团队里新手多、项目复杂度高,YAML 反而是容易埋雷的领域。相比之下,JSON5 和 HOCON 在语法上更“显式”,出问题更容易定位。
2.4 HOCON:为配置文件而生的少壮派
HOCON(Human-Optimized Config Object Notation)是 Typesafe(现在的 Lightbend)为 Play Framework 和 Akka 设计的一套配置格式,后来被 Lagom、sbt 等项目广泛采用。它在很多方面跟 JSON5 类似:允许注释、允许尾随逗号、允许不带引号的键名。但它有两个更“服务端友好”的特性:
第一,支持长配置切分。HOCON 里可以用 include "db.conf" 把其他配置文件引进来,这对于大型项目把配置拆分到多个文件、按环境隔离,非常有用。第二,支持键值的合并与覆盖。基础配置里说 timeout=10,环境配置里说 timeout=20,HOCON 的合并规则是后面的覆盖前面,天然支持环境差异化,不用引入额外的环境变量解析逻辑。
HOCON 的解析底层由 config 库(Java/Scala 生态的 ConfigFactory)实现,在 JVM 社区几乎算标配。如果你在写 Java 或 Kotlin 服务,直接在 application.conf 里用 HOCON 语法,比维护一堆 JSON 舒服一个档次。
2.5 方案选型对比:不要盲目跟风
| 方案 | 注释 | 尾随逗号 | 键名不引号 | 跨语言支持 | 生态成熟度 | 适合场景 |
|---|---|---|---|---|---|---|
| 原生 JSON | 不支持 | 不支持 | 不支持 | 全语言 | 极高 | 机器间数据交换 |
| JSONC | 支持 | 不支持 | 不支持 | 主要在 JS/IDE | 较高 | VS Code 配置、工具链配置 |
| JSON5 | 支持 | 支持 | 支持 | 主流语言均有库 | 高 | 前端/Node 项目配置文件 |
| YAML | 支持 | 不需要 | 支持 | 全语言 | 极高 | 基础设施编排、CI/CD |
| HOCON | 支持 | 支持 | 支持 | JVM 生态为主 | 中高 | JVM 服务端配置 |
我的经验是,选择不能只看特性列表,要看你所在的整个团队技术栈和工具链。全 JS 项目用 JSON5 很顺手,因为构建链本身就有 Babel 加持;Java 后端用 HOCON 很自然,因为 ConfigFactory 从设计第一个版本起就在考虑多环境覆盖;如果你在做 DevOps 相关,跟随社区主流上 YAML 反而是合理决策。关键是先想明白“谁来写这个配置文件”“谁会读这个配置文件”,再决定格式。
3. 实战接入:在不同技术栈中用带注释和尾随逗号的配置
3.1 前端/Node.js 项目:用 JSON5 替代 JSON.parse
在 Node.js 项目里,把 package.json 换成 JSON5 是不可行的,因为 npm 和 yarn 要求它必须是严格 JSON。但这不意味着我们没法在项目里使用 JSON5——只要把配置文件独立出来,比如建一个 config.json5,在启动流程里用 JSON5 解析即可。
具体做法:
bash复制npm install json5 --save
在入口文件里:
javascript复制const JSON5 = require('json5');
const fs = require('fs');
const raw = fs.readFileSync('./config.json5', 'utf8');
const config = JSON5.parse(raw);
console.log(config.server.port);
如果配置文件里既有注释又有尾随逗号,这段代码都能正常解析。实测下来,json5 包对错误信息的提示很“懂人话”,比如它会告诉你“这里期望一个键名,但实际看到了 }”,比原生 JSON.parse 的“Unexpected token } in JSON at position 42”直观不少。
如果你的项目是纯浏览器环境,也一样能用 json5 包的浏览器版本,或者通过打包器把它打进去。我在 Vite 项目里用过 JSON5 配置一个多环境的“特性开关”清单,体验很好——因为尾随逗号的存在,每次后面加新开关时,不用在前一行上动手术,Git diff 也干净很多。
还有一点要提醒:如果你的项目使用 TypeScript,记得给 json5 安装对应的类型声明:
bash复制npm install @types/json5 --save-dev
不然 tsc 会直接对你抛出一个“无法找到模块的声明文件”的警告。
3.2 VS Code 与工具链里的 JSONC 实践
VS Code 的 settings.json、launch.json、tasks.json 都支持 JSONC。这是微软官方对“配置文件需要注释”这一需求的落地。你在编辑器里写这些文件时,IntelliSense 对注释和格式化的支持都很完善。
但要注意一个细节:当配置项经过 prettier 等格式化工具时,要确保格式化器输出的是 JSONC 而不是删掉注释的 JSON。Prettier 在格式化 *.json 时默认不会主动保留注释,甚至会报错;你需要把文件后缀改成 .jsonc,或者在 Prettier 配置里针对文件类型设置 parser 为 jsonc。这属于踩坑细节,我见过不止一个同事因为格式化工具直接把注释全清了,还以为是缓存问题。
另外,如果你们的团队日常用 VS Code 做前端开发,我建议在项目根目录放一个 .vscode/extensions.json,把对 JSONC 友好的格式化插件(比如 vscode-extensions.json)列入推荐清单,新人入职打开项目时就会自动安装,避免他们用默认格式化器把团队配置改得乱七八糟。
3.3 Python 项目中处理带注释的配置文件
Python 生态里有一个非常受欢迎的配置库叫 python-json5,它实现了 JSON5 语法,并且可以无缝替换 json 标准库。用法也很简单:
bash复制pip install json5
python复制import json5
with open("config.json5", "r", encoding="utf-8") as f:
config = json5.load(f)
print(config["server"]["port"])
还有一个常见场景是为大型项目做“带注释的默认配置样板”。很多 Python 项目为了让用户方便上手,会提供一个 config.example.json5,里面注释写满了每个字段的说明。这个文件不能直接被程序读到,需要读取时单独复制成 config.json——但用 JSON5 的话,直接复制过去就能解析,注释自然会被解释器忽略,完全省去“去注释”这一步。
3.4 JVM 生态:HOCON 与 ConfigFactory
在 JVM 生态中,HOCON 的接入几乎是无痛的。你只需添加依赖:
code复制com.typesafe:config:1.4.2
然后把配置文件命名为 application.conf 放在 classpath 下,在代码里用:
java复制import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
Config conf = ConfigFactory.load();
System.out.println(conf.getString("database.url"));
HOCON 的注释语法和 JSONC 类似,也支持 // 和 #,尾随逗号则是“可直接写”的,不需要额外处理。这个库还有一个非常实用的功能:ConfigFactory.parseFile(File) 可以从任意路径加载配置文件,方便做多环境切换。我早期在 Play 项目里踩过的坑是:配置文件里写了中文注释,但文件编码是 GBK,编译后读取乱码。规范做法是统一用 UTF-8 编码保存,同时在 build 配置里指定文件编码,或者在启动参数里加上 -Dfile.encoding=UTF-8。这里不展开讲编码细节,但想提醒一句,凡是配置里出现非 ASCII 字符的团队,都应该尽快把编码规范立起来。
3.5 纯运维脚本场景:JSON 转 JSONC 的轻量离线处理
不是所有项目都有心思引入一个配置解析库。有时你只是在写一个临时 Shell 脚本,需要解析一个带注释的 JSON。此时可以用一个小工具 strip-json-comments,它本身是 JS 写的,但可以单独处理文本流:
bash复制cat config.jsonc | npx strip-json-comments | jq .
jq 是 JSON 处理命令行工具,但它不认注释;strip-json-comments 负责把注释剥掉。两个管道一接,纯 JSON 工具链就能处理 JSONC 了。这个组合在我处理一些其他项目生成的“带注释 JSON 模板”时救过几次命,比打开文件手动删注释快得多。
如果你是纯 Python 环境,也可以用 json5 的 loads 配合 json 标准库,把注释剥掉后再序列化:
python复制import json5, json
with open("config.json5", "r", encoding="utf-8") as f:
data = json5.load(f)
# 转成标准 JSON 字符串供其他工具使用
print(json.dumps(data, ensure_ascii=False, indent=2))
这个小技巧适合做“配置翻译层”:对外接口需要标准 JSON,但内部维护用 JSON5,一次转换即可。
4. 常见问题与排查技巧实录
4.1 注释导致的解析报错:先区分“解析器不认”和“前缀错了”
很多人把“注释报错”一股脑归咎于“格式不支持”,但实际上,很多报错是注释符号本身写得不规范。比如 // 注释 在 JSONC/JSON5/HOCON 中都被支持,但在某些只支持 # 注释的工具里就会报错(例如 Python 的一些自定义配置加载器)。反过来,# 注释在 HOCON 里支持,但在 JSON5 里并不标准。
遇到解析报错,第一件事是确认你的解析器版本和它支持的注释风格。我把这个排查流程固定下来了:先在官方文档查当前解析器支持哪些注释符号,再检查文件头部是否误加了 BOM。UTF-8 BOM 在某些解析器里会被当成非法字符,直接报“unexpected token”,而它和注释其实毫无关系。这类问题我至少遇到两三次,每次都要多花几分钟才能定位。
4.2 尾随逗号兼容性:从浏览器到 Node 的残酷现实
尾随逗号在 JavaScript 语言里从 ES2017 开始合法,在函数参数里 ES2017 也允许了,但如果你在旧版 Node 运行时(比如 Node 8 之前)直接加载带尾随逗号的 JSON 文件,require() 会直接报错。即便到了 Node 20,fs.readFileSync + JSON.parse 依然严格拒绝尾随逗号,因为 JSON 规范没变。
所以在引入 JSON5 时,我见过最典型的坑是“JSON5 解析成功了,但后续代码又用 JSON.parse 二次解析”。比如有人在中间层把 JSON5 串行化成字符串传到另一个服务,另一个服务用标准 JSON 解析,结果当然失败。我的建议是:一旦决定用 JSON5,就要在整个“配置读取链路”上统一解析器,不要让 JSON5 的产物流向下游标准 JSON 解析器。
另一个需要留意的点是:如果团队早期用的是严格 JSON,后来才迁移到 JSON5,项目里很可能出现“同一份配置,部分文件是 JSON,部分是 JSON5,解析器却混着用”的情况。迁移落地时最好写一个小脚本,把所有 .json 文件批量改成 .json5,并检查全部引用了配置加载代码的文件。不要靠手工一个个改,容易漏。
4.3 权限与安全视图:不要让“宽松解析”变成“攻击面”
配置文件的解析并不仅仅是语法问题,还关联到安全。JSON5 的解析器虽然宽松,但也正因支持单引号、不带引号的键名等特性,让它与原生 JSON.parse 相比有更大的“词法面”。如果你的配置文件内容来自外部不可信来源(比如用户上传的配置文件),一定要谨慎使用宽松解析,避免解析器在极端输入下出现不可控行为。
更常见的安全问题是:有些团队为了让“配置支持注释”,在加载配置前盲目执行“正则去注释”,然后交给 JSON.parse。这种做法极其危险,因为正则很难覆盖所有注释写法,更难处理字符串中出现的 // 或 /*。一个恶意构造的配置字符串可以篡改注释过滤逻辑,导致配置数据被意外修改。所以我一般不推荐“正则去注释 + JSON.parse”的组合,不如直接引入一个正经的解析器。如果确实有安全顾虑,可以先把 JSON5 解析后的对象做白名单校验,只允许特定字段被读取。
4.4 工具链支持:你格式化的时候,注释会不会失踪
这是一个实操体验差距很大的领域。VS Code 内置的 JSON 格式化工具对 JSONC 处理得不错,能保留注释;但很多 CI 流水线里的格式化工具(比如 prettier)在针对 .json 后缀文件时,默认会移除注释。配置必须保存为 .jsonc 后缀,或者用 // prettier-ignore 注释标记来避免被“暴力格式化”。
对,prettier 也有 JsonC 支持,但它是通过 --parser jsonc 来触发的。例如在 .prettierrc 里:
json复制{
"overrides": [
{
"files": "*.jsonc",
"options": {
"parser": "jsonc"
}
}
]
}
如果没有这个配置,保存时尾随逗号也会被自动删掉,注释会消失。团队新人很容易踩这个坑,最后 git diff 一团糟。最好从一开始就把 .prettierrc 和 .editorconfig 配好,让所有成员在编辑器端就统一行为。
4.5 常见问题速查表
| 问题现象 | 可能原因 | 排查手段 |
|---|---|---|
JSON 解析报 Unexpected token / |
把 JSONC/JSON5 文件交给严格 JSON 解析器 | 换成支持注释的解析器或先剥注释 |
| 尾随逗号在 CI 里报错 | CI 环境用了旧版 Node 或直接 JSON.parse | 统一使用 JSON5/HOCON 解析;检查 CI 镜像的 Node 版本 |
| 注释文本有中文,读取成乱码 | 文件编码不是 UTF-8 | 用 UTF-8 重新保存;启动参数补充编码配置 |
| Prettier 保存后注释没了 | 格式化器 parser 不是 jsonc | 在 .prettierrc 中设置 overrides |
配置文件加载后字段变成了 undefined |
注释里写了 _comment 之类字段且代码在读取 |
确认不会把注释键传给业务逻辑;解析后用白名单过滤 |
| 用正则去注释后 JSON 被破坏 | 正则误伤了字符串内部的 // |
放弃正则方案,改用正式解析器 |
4.6 大型项目里落地“宽容配置”的一个建议
如果你们是一个中大型项目,我建议不要直接把所有配置都切到 JSON5,而是采用“分层策略”:最外层用标准 JSON 或 YAML 做接口契约,内部私有配置文件用 JSON5/HOCON,提高开发体验。也就是我常说的“对外严格,对内宽松”。对外接口如果允许任意方言,会让依赖方无所适从;但对内的配置文件,完全可以拥抱带注释、带尾随逗号的宽松格式,把效率先提起来。
还要记得在代码评审规范里写明“配置文件不允许有魔法数字、必须带注释”,这比任何技术选型都更重要。很多时候,配置文件的可读性问题不是格式造成的,而是团队没有养成给配置写注释的习惯。选一个更宽容的格式,只能说提供了土壤;能不能长出注释文化,还得靠规范和自觉。
5. 从 JSON 到宽容配置的迁移清单
如果看完前文你已经决定在项目里启用 JSON5 或 HOCON,下面这份迁移清单可以帮你减少阵痛。
- 盘点现有配置文件:找到所有需要支持注释/尾随逗号的文件,按“机器生成”“人工维护”分组。机器生成的文件不建议迁移,人工维护的尽快迁移。
- 统一解析库:每个语言选一个 JSON5 或对应格式的官方解析库,锁版本。
- 写一个批量转换脚本:把
.json后缀改成.json5或者application.conf等新后缀;把文件头部格式统一为 UTF-8。 - 修所有引用路径:搜索原来的
.json硬编码路径,更新为新路径和对应加载逻辑。 - 跑一轮完整的加载测试:尤其是多环境配置的覆盖逻辑,确认解析器切换没有导致字段丢失或默认值变化。
- 配置格式化规范:编辑器、Prettier、CI 中的格式检查全部对齐到新格式。
- 在 README 或社区 Wiki 里写清楚“本仓库配置格式”和“如何加注释”,避免后来者继续按旧习惯写严格 JSON。
我在实际项目中,最耗时的是第 2 步和第 5 步。很多 JSON5 库在解析深层嵌套对象时,性能和严格 JSON.parse 有差距,虽然不大,但在启动阶段高频读取时仍然值得压一遍。另外,如果你同时用了环境变量覆盖配置的逻辑,一定要在切换解析器后,重点确认环境变量合并的优先级没有变化。
写在最后
回到最开始的问题:JSON 值得被“抛弃”吗?我的看法是,JSON 作为数据交换格式,它的严格性依然是非常好的特性;但在作为“人写的配置文件”这个场景里,它确实有些不合时宜。我们不是要打倒 JSON,而是要给配置文件正名——它应该允许注释,应该允许尾随逗号,应该让维护者少一点无意义的体力劳动。
我个人在实际操作中的体会是:选格式前先观察你们团队到底痛在哪。如果只是想在 VS Code 里写注释,JSONC 就够了;如果经常被尾随逗号折腾,那就直接上 JSON5 或者 HOCON;如果是做基础设施配置,YAML 也没问题。关键是让配置回归本源:方便人读、方便人改、方便机器解析。三者平衡好了,项目的维护体验会有一个非常直观的提升。最后再分享一个小技巧:无论选哪种格式,都别忘了在项目里加一个“最小配置样例”文件,里面把注释写满,跑通后再删掉注释,这才是保证配置文件可维护性的终极药方。
