1. 这个痛,每个写配置的人都遇到过
先说个我自己的经历。之前维护一个数据同步服务,配置文件是标准 JSON,里面有个字段是目标表名。某天同事手动往配置里加了一个新表,手滑在最后一个字段后面多留了一个逗号——就是那种肉眼几乎看不出来的尾随逗号。结果服务启动后反序列化直接抛异常,他盯着配置看了十分钟愣是没发现哪里错了。后面我过去一看,好家伙,标准 JSON 规范里根本不允许尾随逗号,解析器一概报错。那一整天的排查成本,就栽在一个逗号上。
另一个更常见的场景,是配置里想写注释。做大数据同步的应该都有体会,DataX 那种 JSON 格式的作业配置,字段一多,想给每个字段写一行说明,结果发现 JSON 压根不支持注释。你写个 // 上去,解析器直接红脸。最后只能用 "字段注释" 这种 key 硬塞进去,等运行时再忽略掉。丑是丑了点,但确实没别的办法。
这其实就是标准 JSON 用来做配置文件时的先天缺陷:它是一门严格的数据交换语言,不是给人手写维护的配置格式。JSON 设计之初的定位是机器与机器之间、程序与程序之间交换数据,语法严格到近乎苛刻,为的是序列化和反序列化时没有任何歧义。但配置文件不一样,配置文件是给人看的、给人改的,人需要注释来解释每个字段的含义,也需要容忍尾随逗号这种小失误。
这个矛盾怎么解决?业界其实已经有一堆答案了:JSON5、JSONC、HOCON、YAML,或者干脆自己写一个宽松版 JSON 解析器。这篇我就把我这几年的实践经验完整梳理一遍,从方案选型对比到具体代码实现,再到生产环境里的坑,一次讲透。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 先搞清楚问题本质:JSON 的严格语法到底卡在哪
在聊解决方案之前,我建议大家先花两分钟把 JSON 的语法限制想清楚。因为很多人的需求其实没有一开始想的那么复杂,搞清楚限制在哪里,才能判断自己到底适合引入重型依赖还是写个小工具函数就够了。
2.1 标准 JSON 对配置文件不友好的三个具体表现
标准 JSON 的语法规定其实很短,RFC 8259 里写得很清楚。但就是这几条规定,在配置场景下处处扎手。
第一,不允许注释。JSON 的语法树里根本不存在注释节点。// 和 /* */ 哪怕出现在字符串外面,也都会被当成非法字符。这直接导致了一个特别反直觉的事实:JSON 的发明者 Douglas Crockford 后来在多种场合说过,JSON 设计成这样是为了防止“注释被用来破坏兼容性”——因为各家解析器对注释的处理不一样,就会产生方言。
第二,不允许尾随逗号。数组和对象的最后一个元素后面多了一个逗号,语法检查直接失败。但人写东西的习惯偏偏就是会在最后一项后面顺手加个逗号,尤其是从别的语言复制粘贴过来的时候。
第三,字符串必须双引号。单引号、反引号、不带引号的裸 key 在标准 JSON 里都是非法的。写配置文件的时候,尤其是从 JavaScript/TypeScript 习惯转过来的人,很容易写顺手。
2.2 配置文件的真实需求清单
结合我这些年用过的各种配置系统的经验,配置文件场景的真实需求其实可以列成一张清单:
| 需求 | 标准 JSON | 说明 |
|---|---|---|
| 注释解释字段含义 | 不支持 | 团队协作中最刚需的能力 |
| 容忍尾随逗号 | 不支持 | 减少手工编辑时的心智负担 |
| 多行字符串 | 不支持 | 写 SQL、正则、密钥块时极其不便 |
| 单引号/裸 key | 不支持 | 与日常写代码的习惯有冲突 |
| 可读性 | 尚可 | 嵌套深了确实难读 |
| 与 JSON 生态兼容 | 完全兼容 | 最大的优势 |
所以你看,需求其实分两类:一类是“最好有”的(注释、尾随逗号),另一类是“锦上添花”的(单引号、裸 key、多行字符串)。不同的人对这两类的需求程度不一样,这直接影响后续选型。
3. 主流宽松方案的横向对比,以及我为什么最终选了自研
如果你也有“JSON 配置文件想加注释和尾随逗号”的需求,市面上能用的方案大致有这几类。我一个个说,每个都说说优点和暗坑。
3.1 JSON5:功能最全的 JSON 超集
JSON5 是 JSON 的官方超集,设计目标就是把 JSON 扩展成 ES5 语法的一个子集。它支持注释(行注释和块注释)、尾随逗号、单引号字符串、双引号字符串、裸 key、十六进制数字、正负 Infinity、多行字符串。
如果你用的是 Python,有 pyjson5;如果是 JavaScript,有官方 json5 包;Java 也有 org.json:json 的扩展版本。功能确实全,几乎能解决我上面列的所有痛点。
但 JSON5 有一个问题:它把语法放宽太多了。裸 key、单引号、十六进制数字这些特性,在很多场景下是好事,但在配置管理场景下反而是风险。配置文件的编写者可能是运维、数据分析师,不是程序员,语法太灵活意味着不同人写出来的配置风格千差万别,规范化成本很高。而且,如果你的配置需要被多个语言(比如 Python 服务 + Java 服务 + Node 脚本)同时读取,语言之间的 JSON5 解析实现不完全一致,字段顺序、错误报文都不太统一,排查问题的时候会比较难受。
3.2 JSONC:主要是编辑器生态在用
JSONC,全称叫 JSON with Comments,是 VS Code 这一派推进出来的概念。它允许在 JSON 里写注释(通常是 //),但其他地方保持标准 JSON 的语法要求——尾随逗号仍然不允许。
这个东西的好处是易实现。你写一个小的注释剥离器(把注释去掉再丢给标准 JSON 解析器),100 行以内就能搞定跨语言版本。坏处也明显:没有标准化规范。不同编辑器支持的 JSONC 注释语法不完全一致,有的支持 //,有的只支持 /* */。而且在标准 JSON 解析器面前,JSONC 不是一个合法的 JSON,所以如果你只是想要“稍微宽松一点”,JSONC 是个轻量选择;但如果你还想要尾随逗号,它就无能为力了。
3.3 HOCON:为配置而生的格式
HOCON(Human-Optimized Config Object Notation)是 Typesafe(现在叫 Lightbend)公司为 Scala/Java 的 Typesafe Config 库设计的。它支持注释、尾随逗号、可以省略引号、支持变量替换和文件 include。如果你写的是 JVM 系的应用,HOCON 确实是一个非常优秀的配置格式。
但 HOCON 的问题是:生态绑定太死。在 Python、Node 里用 HOCON,解析器都是第三方移植的,成熟度和维护频率参差不齐。而且 HOCON 的语法远比 JSON 复杂,学习成本高。如果你的团队不是 JVM 系,引入 HOCON 的收益并不大。
3.4 YAML:另一个方向的答案
YAML 的定位就是“比 JSON 更适合人类书写”。它天然支持注释,不需要尾随逗号(因为列表和对象靠缩进表达),字符串也不用引号。我确实见过很多项目把配置文件从 JSON 迁到 YAML,感觉确实舒服。
但 YAML 的问题也有两个。一是缩进敏感,多一个空格少一个空格就解析崩溃,这个问题在复制粘贴场景里特别烦人。二是类型解析太聪明,on、yes、no 会被解析成布尔值,时间字符串会被解析成日期对象,这种隐式类型转换在配置场景里很容易埋下隐蔽的 bug。后来 YAML 1.2 规范里修掉了一部分,但很多语言的实现还是旧行为。
3.5 我的选型结论
我最终的选择是:自研一个轻量的“JSON 宽松版解析器”,只放宽两个点——注释和尾随逗号。
理由很简单:第一,这两个点解决了 80% 的配置
