JSON 格式化
把 JSON 粘进来,美化、压缩,或者查清楚它到底哪里不对。能解析的时候,还会多跑两项普通解析器不会告诉你的检查。
发现重复的键
重复的键在 JSON 里是合法的,而所有解析器都只保留最后一个、且不报错。如果你改了一个值却发现没生效,通常就是这个原因。下面列的是路径而不是孤零零的键名:x.a 表示键「x」所指对象里的键「a」,items[0].a 表示数组「items」第一个元素里的键「a」。
超出精确表示范围的整数
JavaScript 用 64 位浮点存数字,超过 2^53 − 1 的整数在被解析时就会丢精度。这些值无论经过本工具还是经过任何 JavaScript 程序都无法原样保留。如果它们是 ID,请在数据源头就用字符串存。
粘贴点内容开始,或者载入示例看看这两项检查是干什么的。
使用方法
粘贴 JSON
输入时即时校验,粘错了立刻标出来,不用等到点按钮。载入示例可以看到那两项额外检查具体在查什么。
处理它报出来的问题
语法错误会给出行号、列号,并用插入符指出具体位置。重复键和超范围整数不影响解析,所以它们显示在结果下方的提示区里 —— 即使 JSON 合法,那两块也要看一眼。
选缩进并格式化
两个空格、四个空格或制表符。需要结果可复现(比如对比两个文件、或比对哈希)就勾上"按键名排序"。结果和输入并排显示,复制之前可以先核对。
复制或下载
复制把格式化结果放进剪贴板,下载存成 .json 文件。如果目标是更小的传输体积而不是给人看,用"压缩"把所有空白去掉。
两个能正常解析、却已经毁掉数据的问题
只回答"这段 JSON 合法吗"的校验器,回答的是一个比你真正关心的问题更容易的问题。下面两种情况都是完全合法的 JSON,都能一声不响地解析成功,而解析出来的东西都不是文件里写的那个。
第一个是重复键。`{"port": 3000, "port": 8080}` 是合法的。所有语言的所有解析器都只保留最后一个,其余的直接忘掉,不报错、不警告。这个 bug 在实际工作中的样子是:有人改了配置文件、保存、重启,发现行为没变 —— 因为文件里还有一个位置更靠后、长得一模一样的键,把那次修改盖掉了。语法高亮看不出来,校验器也看不出来。
第二个是超出 2^53 − 1(即 9007199254740991)的整数。JavaScript 用 64 位浮点存所有数字,比这更大的整数无法精确表示。粘一个 9007199254740993 进去,解析器交给你的是 9007199254740992。没有报错,没有警告,而且这个数字看上去很合理 —— 它只错在最后一位,而最后一位恰恰是那种能一路通过所有检查的错误。数据库的 bigint、订单号、以及几个大平台用的雪花 ID,全都落在这个区间里。
这种损坏是单向的。数值一旦被解析成不精确的浮点,再序列化出来写的就是那个被舍入过的数,所以"读出来—改一下—写回去"这个循环会永久性地毁掉原始值。解析之后的值里没有恢复的可能,只能从原文恢复 —— 这也是这里的两项检查直接扫原文、而不是检查解析结果的原因。
为什么这个工具只提醒,不替你改
看起来很"贴心"的做法是:把这些超范围整数自动转成字符串,因为带引号的 `"9007199254740993"` 能原样保留。我们刻意不这么做。
那会静默改变你数据的类型。期待数字的一方拿到字符串,比较和运算的行为都会变,下游的 schema 校验会失败 —— 或者更糟,被悄悄强制转换。这和我们要防的是同一类错误:语法上完全说得通,而你的数据已经被改掉了。悄悄重写你的数值的工具,并不比悄悄舍入它们的工具更安全。
所以这里列出有问题的字面量、显示每个值被解析成了什么,把决定权留给你。真正的修法在数据源头:如果一个值是用来标识的、而不是拿来算的,产出 JSON 的那一方就应该把它序列化成字符串。ID 是标签,两个 ID 相加没有任何意义。从源头就按字符串编码,能把这一类问题对所有下游消费者一次性解决,而不只是对本工具有效。
JSON 的语法错误通常从哪来
绝大多数非法 JSON 出自一个"脑子里在想 JavaScript"的人,而 JavaScript 宽松得多。按出现频率大致排序:
- 多余的逗号。`{"a": 1, "b": 2,}` 在 JavaScript 里没问题,在 JSON 里非法。这是最常见的一条,通常是删掉列表最后一项时把逗号留下了。
- 用单引号包字符串或键名。JSON 要求一律用双引号。
- 键名没加引号。`{a: 1}` 是 JavaScript 的对象写法,不是 JSON。
- 注释。`//` 和 `/* */` 都不是 JSON 的一部分 —— 所以一个在某个工具里能读的配置文件,换个地方就报错,因为那个工具读的可能是 JSON5、JSONC 或 HJSON。
- `NaN`、`Infinity`、`undefined`、十六进制数字。JSON 里都没有。Infinity 在 JSON 里根本无法表示为数字,通行做法是写成 `null`。
- 字符串里有裸换行或制表符。JSON 不允许字符串里出现字面控制字符,必须写成 `\n`、`\t` 这样的转义。
怎么正确看待报出来的位置
报出来的位置是解析器放弃的位置,不一定就是出错的位置。两个成员之间少了一个逗号,解析器是在读到第二个成员、发现无事可做时才察觉的 —— 所以插入符会落在真正问题的后一个记号上。如果那个位置看上去毫无问题,请往上一行看。
不同内核的报错也不一样:Chrome 和 Node 给字符偏移,通常还有行列;Safari 的措辞又是另一套。这里读取浏览器给出的信息;如果报错里没有位置,就原样显示那条消息 —— 有些错误确实没有位置,编一个行号出来比承认没有更糟。
插入符会截取出错那一行连同上下文一起显示,因为在一个四百字符的长行里,只给一个列号是没法用的。如果完全没有位置信息,通常的嫌疑是:粘贴被截断了、文件最开头有 BOM(从 Windows 编辑器保存的文件里粘贴时常遇到)、或者混进了不间断空格 —— 它看起来和普通空格一样,但它不是合法的 JSON 空白字符。
几个不起眼但真的会影响事情的格式化选择
缩进对机器是装饰,对人不是。两个空格在 JavaScript 项目里更常见,别的圈子里四个空格也有,这个选择不影响 JSON 的行为。
真正要注意的是压缩。它适合走网络的传输载荷,不适合之后还要由人来编辑的文件 —— 压缩后的 JSON 是巨大的一行,对比(diff)毫无用处,合并冲突时也根本无法处理。文件如果要进版本控制,就保持格式化。
按键名排序这件事值得专门提醒。JSON 规范说对象成员的顺序没有意义,对多数使用方来说也确实如此。但现实里有大量系统会比较序列化之后的字符串:对字符串化对象算的哈希或签名、快照测试、以及会校验文件校验和的配置。在这些场景打开排序,输出就变了,可能让签名失效或让测试失败 —— 尽管语义上什么都没变。为了 diff 好看可以排序,要拿去签名的东西不要排。
最后更新:
常见问题
我的 JSON 会被发到服务器校验吗?
不会。解析、格式化、压缩以及那两项额外检查全部在页面里完成。这一点在这里比其他工具更要紧:粘进格式化工具的 JSON,经常是带密钥的配置文件、带 token 的接口返回,或者客户数据。不传输、不存储、不记录。
它说 JSON 合法,却又给出警告,到底以哪个为准?
两个都对。它的语法合法,在任何地方都能解析;警告说的是解析过程会对你的数据做什么 —— 一个会被丢掉的重复键,或者一个无法精确表示的整数。语法合法和数据完好是两个问题。
为什么 9007199254740993 变成了 9007199254740992?
因为 JavaScript 用 64 位浮点表示数字,只能精确容纳 2^53 − 1(即 9007199254740991)以内的整数;超出之后会静默取最接近的可表示值。如果这个值是标识符,彻底的修法是让数据源头就按字符串输出它。
格式化会改我的数据吗?
格式化和压缩只改空白,解析出来的值完全一致。唯一需要想一下的是按键名排序:值没变,但输出字符串变了 —— 如果下游要拿它算哈希或签名,这件事就有影响。
能用它把 JSON 转成 CSV、YAML 或 XML 吗?
不能,这个工具只处理 JSON。格式互转是另一件事,失败方式也不一样 —— 尤其是嵌套结构,在扁平表格里根本没有自然对应的表示。
为什么带注释的 JSON 被拒绝了?
因为注释不属于 JSON。`//` 和 `/* */` 来自 JSON5、JSONC、HJSON 这些方言,部分工具接受它们。一个在打包工具里能正常加载的配置文件,对严格解析器来说可能就是非法的。如果这份文件要交给只认 JSON 的东西,把注释去掉。
能处理多大的文档?
不设固定上限。解析和那两项额外检查都是对文本的线性扫描,几 MB 的接口返回在现代机器上没问题。超大输入会让结果框第一次渲染时卡一下 —— 那是浏览器在排版一整个很长的字符串,不是分析卡住了。