JSON校验工具

逐字符走一遍你的 JSON,语法出错时直接说出第几行第几列出错、那一行长什么样、这类写法的正确形式是什么;通过时给出值的个数、最大嵌套深度和 UTF-8 字节数。

值的个数11
最大深度2
顶层类型对象
行数8
码点数136
UTF-8 字节148

提示:输入框里按 Ctrl + Enter(macOS 用 Cmd + Enter)立刻校验一次;单独按 Enter 是普通换行,不会触发。

下表 19 条样例的位置由本页同一个状态机跑出,格式为「行:列」;表里的 ↵ 表示真实换行(不是字符 \n)。

错误样例状态机检出原因
{"a":1,}1:8对象尾随逗号
{'a':1}1:2键名用了单引号
{"a":1 // 注释 ↵ }1:8出现行注释
{"a":01}1:7数字前导零
[1,2,]1:6数组尾随逗号
{"a":undefined}1:6出现 undefined
{"a":NaN}1:6出现 NaN
{"a":1 "b":2}1:8两个成员之间缺逗号
{"a":"x}1:9字符串未闭合
{"a":1e}1:8指数缺少数字
{"a":.5}1:6小数缺整数部分
{"a":"甲↵乙"}1:8字符串里有真实换行

使用方法

  1. 把 JSON 粘进上面的输入框,或直接在里面敲。页面打开时已经放了一段 8 行、11 个值、最大嵌套 2 层的合法示例,右侧统计卡显示的就是它的结果。
  2. 在输入框里按 Ctrl + Enter(macOS 用 Cmd + Enter)立刻校验一次;单独按 Enter 是换行,因为我们不希望你在手工排版 JSON 时被反复打断。
  3. 校验不通过时,「校验结论」里会给出第几行第几列,下面贴出出错那一行的原文和一个 ^ 指示符,指到具体是哪一列;再往下是浏览器原生 JSON.parse 对同一段文本的说法,便于对照。
  4. 点「载入错误示例」会填入一段故意写错的 JSON——第 3 行的数组里多了一个逗号,本工具报的是 3:25,而浏览器原生报错只给出一小段截断的文本,没有任何数字可循。
  5. 下方的 19 条对照表是同一个状态机跑出来的历史样例,点「加载更多」展开全部,「导出 CSV」拿到带 BOM 的表格文件,「复制对照表」直接以 TSV 形式进剪贴板。

这个 JSON校验工具能解决什么问题

一是把「解析失败」翻译成能动手改的位置。接口返回 400 时经常只有一句 Unexpected token ']', "[1,2,]" is not valid JSON,连行号都没有;浏览器控制台里的提示同样时有时无。这个 JSON校验工具用逐字符状态机把出错点换算成第几行第几列,再配一句中文的原因说明,你直接跳到那一列改就行。

二是把「语法错」和「值不对」分开。校验只管语法:字段该不该是数字、时间字符串格式对不对、枚举值合不合规,这些语法层面看不出来,本页也不判。反过来说,一段语法完全合法却被服务端拒绝的 JSON,问题不在校验这一步,别在这里空转。

三是核对程序拼出来的 JSON。配置文件、埋点上报、导出报表常常是代码拼串生成的,肉眼很难看出少没少一个逗号。先过校验,再看值的个数、最大嵌套深度和行数对不对——示例里 8 行文本对应 11 个值、2 层嵌套,数字对不上就说明结构和你以为的不一样。

四是拿真实样例反推写法。对照表里 19 条都是日常最容易踩的坑:尾随逗号、单引号键名、前导零、NaN、字符串里的真实换行……每条都标了状态机检出的行列,你可以照着把手上那段 JSON 的症状对号入座。

本页不做这些事:不按缩进重新排版、不比较两份 JSON 的差异、不做压缩、不做 JSONPath 取值、不检查重复键、不做 JSON5 或 JSONC 这类方言的完整解析。校验通过之后想接着排版,页面底部的「JSON格式化工具」更合适;想按路径取值请用「JSONPath查询」。

校验原理:逐字符状态机、行列换算与 JSON.parse 位置映射

核心是一个递归下降的逐字符状态机。它从左到右把文本走一遍:遇到 { 进入对象上下文、遇到 [ 进入数组上下文、遇到 " 进入字符串上下文,每个上下文里只接受 RFC 8259 规定的那几种后继字符。整个扫描是单次遍历,不建抽象语法树,所以 100 万字符的输入也不会明显卡顿,内存只跟嵌套深度有关。字符串上下文里单独处理转义:只有 \" \\ \/ \b \f \n \r \t 这 8 个单字符转义和 \uXXXX 合法,与 RFC 8259 第 7 节给出的 ABNF 一致;\u 后面必须恰好 4 位十六进制数字,\u4E 会被判非法。

行列是怎么算出来的。扫描时一直维护 i、line、col 三个游标,初始值都是 1,也就是行号列号都从 1 开始而不是从 0 开始。每读一个字符:换行把 line 加一并把 col 归 1,其余字符把 col 加一。\r\n 只让行号加一次,单独的 \r 或单独的 \n 也各算一次换行,所以 Windows 和 Unix 行尾混在一份文件里也不会算乱。出错时抛出的 line、col 是那个不合法字符本身的位置,不是它后面的位置。

为什么不能只靠 JSON.parse 的报错。V8 的报错有时带位置、有时完全不带:{"a":1,} 会给出 Expected double-quoted property name in JSON at position 7 (line 1 column 8);但 [1,2,] 只给一句 Unexpected token ']', "[1,2,]" is not valid JSON,一个数字都没有。所以本页把状态机当主定位器,把 JSON.parse 当复核,两边的说法并排显示在结论里。

position 到行列的换算是另一条独立路径。V8 的 position 是从 0 开始的 UTF-16 码元偏移,不是码点、更不是字节。换算方法是:行号 = position 之前出现过的换行符个数加 1;列号 = position 减去它前面最近那个换行符的下标。因为 JavaScript 字符串下标本身就是码元,一个 😀(占 2 个码元、1 个码点)会让它后面所有列号比你在编辑器里数出来的多 1——这不是算错,而是「列」在 JS 世界里按码元定义。

UTF-8 字节数按码位区间累加:≤ U+007F 记 1 字节,U+0080–U+07FF 记 2 字节,U+0800–U+FFFF 记 3 字节,代理对(补充平面,如 emoji)记 4 字节。示例文本里 6 个汉字各多算 2 字节,所以 136 个码点对应 148 个 UTF-8 字节;这段文本没有补充平面字符,所以按 UTF-16 码元数也是 136。

完整示例(数值仅作演示)

示例条件与页面打开时的默认值完全一致:一段合法 JSON,未做任何修改。状态机走完后的六项实测值是:

同一段文本丢给浏览器原生的 JSON.parse,结论也是通过——两边一致才说明状态机没有误判。

把示例换成一段故意写错的文本,差别就出来了。下面这段第 3 行的数组里多了一个逗号:

{ "name": "柳泉工具", "tags": ["json", "校验",], "version": 2 }

状态机报出的是 3:25——第 3 行第 25 列,也就是那个 ]。JSON.parse 对同一段文本只给出一句 Unexpected token ']', ... is not valid JSON,没有任何数字可循。这就是本页存在的理由:它把「错了」变成「错在第 3 行第 25 列」。

合法与非法写法对照表

下表按 RFC 8259 的 ABNF 逐条列出常见写法。若你在别处看到「JSON 可以写注释」,那说的是 JSONC 或 JSON5 这类扩展,不是标准 JSON。

写法结论依据
{"a":1}合法标准对象,成员名必须是双引号字符串
{"a":1,}非法逗号只能出现在两个成员之间,} 前不能有逗号
{a:1}非法成员名必须是 string,裸标识符不是
{'a':1}非法string 只能用双引号,单引号是 JS 写法
{"a":01}非法number 的整数部分不允许前导零,0 必须单独出现
{"a":0.5}合法小数部分 .5 合法,但整数部分的 0 不能省
{"a":.5}非法number 必须有整数部分
{"a":1.}非法小数点后至少要有 1 位数字
{"a":1e3}合法指数部分为 e 或 E,可带正负号
{"a":1e}非法指数部分必须有数字
{"a":+1}非法number 只允许前导减号,正数不加号
{"a":NaN} / Infinity非法不是 number 产生式,需写成 null 或字符串
{"a":undefined}非法标准 JSON 没有 undefined
字符串里放真实换行非法string 内不得出现未转义的控制字符,要写成 \n
{"a":"\u4E2D"}合法\u 加恰好 4 位十六进制
{"a":"\u4E"}非法不足 4 位,解析器会继续吃后面的字符直到凑满或报错
{"a":1} 后接 // 注释非法RFC 8259 没有注释产生式
{"a":1}{"b":2}非法一个 JSON 文本 = 一个值,值后面只允许空白

输入范围、边界与精度说明

常见问题

为什么浏览器控制台的报错有时带 position、有时只有一段文本?

因为 V8 对不同语法错误给的线索不一样。{"a":1,} 这种「属性名位置读到 }」的错误会附带 at position 7 (line 1 column 8);而 [1,2,] 只会吐出 Unexpected token ']', "[1,2,]" is not valid JSON,还把片段截断成十来个字符。本页的状态机不依赖引擎文案,任何一类错误都给出确定的行列。

尾随逗号为什么报在 } 或 ] 上,而不是那个逗号?

因为读到逗号的那一刻无法判断它是不是尾随——{"a":1, "b":2} 里同样的逗号完全合法。只有继续跳过空白、看到右括号,才能确定「这个逗号后面没有成员了」。所以位置落在右括号上。{"a":1,} 报 1:8,逗号在第 7 列;这跟 Chrome 控制台给出的 position 7 是同一种口径。

校验通过,是不是就说明这段 JSON 没问题了?

只说明语法没问题。重复键、超出 253−1 的整数、孤立代理项、字段该不该为 null,这些本页都不判。反过来也要留意:一段语法合法的 JSON 被服务端拒绝,问题往往在字段类型或业务校验上,不在语法这一步。

{"a":1,"a":2} 这种重复键为什么不报错?

因为 RFC 8259 只规定「成员名应当是唯一的」,把重复时的取舍留给实现,并不要求解析器报错。JavaScript 的 JSON.parse 保留最后一个,Java 的 Jackson 默认也是后者覆盖前者。要查重复键得按成员顺序再扫一遍,那是另一类工具的事。

为什么这个 JSON校验工具不像别的工具那样自动帮我排版?

因为两件事的判定标准不同。排版要重建缩进和换行,属于输出格式问题;校验只回答「这段文本能不能被解析器接受」,必须在原始字符序列上按行列走,一旦重排,报出来的行列就不再对应你手上的文件了。所以本页刻意不改动你输入的任何字符,连 BOM 也只替换成一个空格。

文件开头有个看不见的字符,是它让校验失败的吗?

很可能。Windows 记事本「另存为 UTF-8」会写入一个 U+FEFF 字节顺序标记,肉眼看不到,但 JSON.parse 会把它当成非法字符。本页遇到首字符是 U+FEFF 时会先把它换成空格再校验,并在补充说明里点出来。要彻底清掉,可以用编辑器另存为「UTF-8 无 BOM」。

超出 253−1 的大整数为什么判为合法、解析出来却变了?

因为校验只管语法,而 JSON.parse 会把数字转成 IEEE 754 双精度浮点,只能精确表示到 9007199254740991。9007199254740993 语法没错,本页判通过,但 JSON.parse 之后会变成 9007199254740992。要原样保留就得写成字符串,或改用支持任意精度整数的解析库(例如 Python 的 json 会保留大整数,Java 需要开 USE_BIG_INTEGER_FOR_INTS)。

一次能校验多大的文件?超限会怎样?

输入框上限 100 万字符,按 UTF-16 码元计。超过之后输入框不再接收新内容,已输入的部分不会被截断;扫描是单次遍历,100 万字符一般在一两百毫秒内出结果。如果文件更大,建议先用命令行工具(如 python -m json.tool 或 jq)过一遍,再用本页定位具体那一段。

相关工具