杂记 › 技术笔记 › JSON 少个逗号查半天?报错定位、压缩与转义

JSON 少个逗号查半天?报错定位、压缩与转义

上个月替人查一个起不来的服务。配置是他手写的 JSON,他删掉其中一个节点,程序只回一句 syntax error,不给行号;他从头扫了三遍,看不出少了哪枚标点。同一份文件交给 Python 3.13 的 json 模块,才拿到完整一句:Illegal trailing comma before end of object: line 5 column 22 (char 93)。真凶在第 5 行末尾。

{
    "upstream": "10.0.3.7:8080",
    "timeout": 30,
    "retries": 3,
    "keepalive": true,
}

另一半是接口返回压成一行,两千多字符挤在一起,找一个键的值时眼睛会跟着滑走。

能跑通的 JavaScript 写法,多半不是合法 JSON

JSON 是从 JavaScript 对象字面量里裁出来的严格子集,裁掉的四样恰是最常写回去的:单引号包字符串、裸标识符做键名、末尾挂逗号、写注释。我拿 Node 24 验过,eval 执行 ({'a': 1, /* c */ b: 2,}) 能拿到对象,JSON.parse 却从第 1 行第 2 列就拒绝,指的正是那个单引号。本机脚本走的是宽容的 JS 解析器,服务加载走的是严格的 JSON 解析器。

下表每条错串我都照敲过一遍。Python 3.13 把它们都报在第 1 行,所以表里只留 column;V8 措辞不同但位置接近。

写法问题错误片段Python 3.13 的报错
尾随逗号{"a": 1,}Illegal trailing comma before end of object: column 8
键名用单引号{'a': 1}Expecting property name enclosed in double quotes: column 2
用注释掉一行{"a": 1 /* note */}Expecting ',' delimiter: column 9
字符串里裸换行
{"a": "line1
line2"}
Invalid control character at: column 13
括号没配平{"a": [1, 2}Expecting ',' delimiter: column 12

另外两条:数字写成 NaN 或带前导零,Python 自己读写得回去,交给前端就炸。还有一种是传输错,Content-Type 写成 text/plain,部分客户端不做自动解析,文件本身其实合法。肉眼配括号太慢,我先让解析器报位置:JSON格式化 输入时就给出错行列,能按 2 空格、4 空格或 Tab 摊开,也能压回单行,树形展示可逐层折叠。

行列号怎么读,压成一行为什么定位不动

除行列外,Python 还给一个 0 基的 char 偏移:行数是偏移之前的换行数加一,列数是偏移减去上一个换行的位置。一条 37 字节的串 {"nodes":["a","b","c",],"timeout":30},Python 报 char 21,即第 1 行第 22 列,正落在逗号后那个右方括号上。

开头那份文件里,同一内容 Python 报 line 5 column 22,指着该删的逗号,V8 报 position 95、line 6 column 1,已跳到下一行。报错点标的是解析器读不下去的第一格,该改的往往是它前面那个元素,所以我拿到行列号先往前回一格。压成一行时行数永远是 1,列号动辄上千,得先摊成多行才有意义。没人说得清改了哪一行时,读报错不如把前后两份文本做 文本对比。

压缩省掉什么:按我的样例算一遍

那句减小约 30% 到 50% 体积的说法我不敢照抄,压缩删掉的只有空白,键名和值一个字节不会少。我构造了一份样例:共 191 键、对象套 5 层,值混合字符串、整数、小数、布尔和 null,用 PHP 编成四种形态实测。

形态字节数比压缩单行多占自身体积
压缩为单行2458无无
Tab 缩进3815135735.6%
2 空格缩进4744228648.2%
4 空格缩进6602414462.8%

4144 能拆开核对:行首空白 3716、换行 237,剩下的 191 正好等于键数,因为美化器在每个冒号后补一个空格,数据本体 2458 却没变。换 Tab 后行首空白只剩 929,一个 Tab 就一字节。自己数可以造十行:每行 4 个空格、11 个换行,格式化 145、压平 94,差的 51 正是 40 加 11。

比例只对这份样例成立:同一份内容 Tab 版多三成五,4 空格版近六成三,是前者的 1.731 倍。要不要压平我只看它给谁读:人要改的配置留缩进;响应体省带宽靠 gzip,跟空白不是同一层手段;只有整段要当字符串塞进代码时压平才省事,那样每个换行都得写成 \n,压平后转义少一类。

把 JSON 当字符串塞进代码,转义是按层叠的

串内四种字符要改写:双引号写成反斜杠加双引号,反斜杠写成两个反斜杠,换行写成反斜杠 n,制表符写成反斜杠 t。翻车在层数,日志里的 payload、表单传上来的 body 都是 JSON 套 JSON,套几层就得转义几次。拿一个 5 字节的值连套三层,逐字符可核对:

套 1 层:{"v":"a\"b\\c"}
套 2 层:{"p":"{\"v\":\"a\\\"b\\\\c\"}"}
套 3 层:{"w":"{\"p\":\"{\\\"v\\\":\\\"a\\\\\\\"b\\\\\\\\c\\\"}\"}"}

反斜杠从 3 到 11 再到 31,整串 15、31、59 字节,最后一行里原始那 5 个字符已看不出形状。换成一句带 ASCII 双引号和 Windows 路径的中文值,值本体 47 字节,套到第三层整串 129 字节、46 个反斜杠:每多套一层,已有的反斜杠又要再转义一次。手工解到第三层我会数错,所以解一层核一层,解完跟原始值比对。要从日志里挑这类串,我先用 正则表达式测试 匹配引号边界和成对反斜杠,再交给解析器。

几个被反复问到的

Q:报错只给了偏移量,怎么换算出行列?

偏移是 0 基位置,行数等于它之前的换行数加一,列数等于它减去上一个换行的位置;只有一行时列号就是偏移加一。

Q:我就注释掉一行,为什么整个文件都报错?

JSON 没有注释语法,报错点常落在这行值末尾或下一个键名开头,看着像值写错了。要留说明只能做成一个普通键。

Q:压缩到底能省多少体积?

按上面那组数:191 键、5 层的样例,Tab 多 35.6%、2 空格多 48.2%、4 空格多 62.8%,多的全是空白。给不出样例的百分比可以跳过。

Q:把生产配置粘进去校验,内容会不会传到服务器?

这类文件常带内网地址和密钥,我只用解析全在浏览器本地完成的那类,前面那个入口就是本机解析。

回到那个起不来的服务:改动只是删掉第 5 行末尾一枚逗号,功夫全在让解析器把位置说清楚。顺序也就固定了:先确认读它的是严格 JSON 解析器,再拿行列号往前回一格,然后按给谁读决定缩进,最后只有非塞进字符串不可时才叠转义。

📎 相关文章

PNG 压了三遍只减 426 字节:无损压缩的真实边界 2026-10-22 WebP 转 PNG 后体积涨了几倍:什么时候必须转,怎么把涨幅压住 2026-10-22 截图缩到四分之一,小字糊成一团的三种原因 2026-10-22 同样裁 300×300,头像为什么总偏右下角半格 2026-10-22 按每月 1.7 倍预估 18 个月,脚本和表格为什么给出两个数 2026-10-22