JSON 比较器,免费

比较两个 JSON 对象,以高亮显示的方式查看差异。

没有数据离开您的设备

关于 JSON 比较

JSON 比较(也叫 JSON diff)用于识别两个 JSON 结构之间的差异。与纯文本比较不同,能理解 JSON 的比较器知道键与值,因此可以准确告诉您哪些属性被新增、删除或修改 · 与键的顺序无关。

此工具对两个 JSON 对象进行深度递归比较。它能处理嵌套对象、数组、字符串、数字、布尔值和 null 值。所有处理都在您的浏览器本地完成。

JSON 简史

JSON 的传记比一般编程语言短得多,戏剧性也少得多。这个缩略词诞生于 State Software, Inc.,一家由 Douglas Crockford 和 Chip Morningstar 于 2001 年 3 月共同创办的小公司,目的是构建后来被称为 Ajax 的 Web 应用。第一条 JSON 消息于 2001 年 4 月从 Morningstar 位于湾区的车库里的一台电脑发出。Crockford 并不声称发明了 JSON,他只是把它从 JavaScript 的对象字面量语法里剥离出来,给它一个名字,再做了一个网站。他在 2002 年拿下 json.org,把一份铁路图式的语法挂了上去。在接下来的四年里,JSON 靠口耳相传和零星出现的库实现慢慢扩散。2005 年 12 月,Yahoo! 开始用 JSON 提供它的部分 Web 服务,这是大多数历史学家所说的 JSON 进入主流的时刻。标准化分波次到来:RFC 4627(2006 年 7 月)由 Crockford 亲笔执笔,属信息性而非 Standards Track;ECMA-404 第 1 版(2013 年 10 月);RFC 7159(2014 年 3 月)把 JSON 推上 IETF Standards Track,并放宽了顶层值必须是对象或数组的要求。当前版本是 RFC 8259(2017 年 12 月,现已是 Internet Standard STD 90)与 ECMA-404 第 2 版(2017 年 12 月)。两份文件在语法上有意完全一致;IETF 文本加入了 Ecma 文本刻意省略的安全与互操作指引。

两种算法家族:按行 diff 与结构 diff

计算机科学从 1970 年代起就在思考 diff。Douglas McIlroy(就是发明 Unix 管道的那位 McIlroy)和 James W. Hunt 在 1970 年代初写出了最早的 Unix diff;它在 1974 年随 Unix 第五版发行,背后的算法记录于 1976 年 6 月发布的 Bell Labs Computing Science Technical Report #41。Hunt-McIlroy 建立在「最长公共子序列」问题之上,找两份输入里都按顺序出现的最长的一段行序列;不属于该序列的部分非删即增。十年后,Eugene W. Myers 在《Algorithmica》Vol 1, Issue 2(1986)发表「An O(ND) Difference Algorithm and Its Variations」,把问题重述为「编辑图」上的最短路径问题,这是一个比前作快两到四倍的 Unix diff 实现的基础。今天,git diff 默认使用 LibXDiff 里的 histogram 算法,通常比朴素 Myers 更快,并且产出更易读的输出,尤其是在涉及块移动时。Patience diff(Bram Cohen, 2008)是另一种 LCS 改进,优先选取唯一的「锚点行」。这些都是同一主题的不同变体:按行做 LCS,把输入当作一串不透明的扁平 token 来对待。它们都不知道 JSON 是什么。

结构 diff 家族采取另一种路线:并行遍历两份输入,对原始类型按严格相等比较,对对象键求并集(只在左边为「删除」、只在右边为「添加」、两边都有则递归),对数组按下标遍历。每个位置都进行递归。如果一边数组更长,多出的元素就是增删。类型变化(数字 vs 字符串、对象 vs 数组)形成「类型已变」的条目。这正是 jsondiffpatch 作为基线实现的算法,也是本工具实现的算法。它的优点是输出有意义:每一处差异都带有文档内的路径(user.address.cityitems[3].price)和清晰语义。它的弱点在于第三步(按下标比较数组)只要在长数组靠前位置插入了一个元素,输出就会失真。

JSON diff 里最难的问题:数组

假设你「之前」的 JSON 含有数组 ["alpha", "beta", "gamma", "delta"],而「之后」的 JSON 含有 ["alpha", "new", "beta", "gamma", "delta"]。一个朴素的按下标 diff 会报告四处变更:下标 1 由「beta」变为「new」;下标 2 由「gamma」变为「beta」;下标 3 由「delta」变为「gamma」;下标 4 新增了「delta」。一个人看就会说:只有一处变更,在下标 1 处插入了一项。这正是 1970 年代按行 diff 家族解决的同一个 LCS 问题,只是对象从「文本行数组」换成了「JSON 值数组」。jsondiffpatch 正是这么做的:算出两个数组的 LCS,并相对它输出插入/删除。对元素是原始类型的数组而言效果不错。对元素是对象的数组就不行了:[{id: 1, name: "Alice"}][{id: 1, name: "Alicia"}] 在引用相等的意义下没有公共子序列,所以朴素 LCS 会报告「整体删除左边那一项、整体新增右边那一项」,而事实是某一项的一个字段变了。jsondiffpatch 的解决方案是 objectHash 回调:调用方提供一个把每个对象映射到身份键(一般是 obj => obj.id)的函数,diff 就按身份而不是按引用来匹配数组元素。这是一个没有人完全解决的通用难题。本 Absolutool 工具按其自身 FAQ 所说,是按下标比较数组的,既不实现 LCS 也不实现 objectHash。对一个小型免费工具来说这是一个有意的范围选择:它在对象 diff 上极好,在「数组里插入或重排了元素」的场景下偏弱。

JSON Patch(RFC 6902):把 diff 当作传输格式

RFC 6902「JavaScript Object Notation (JSON) Patch」于 2013 年 4 月发布。它定义了媒体类型 application/json-patch+json 以及由六种操作组成的 patch 语言:add、remove、replace、move、copy、test。一个 patch 本身就是一份 JSON 文档,一个操作对象数组,每个对象都有一个 op 键、一个 path 键(一个指向目标位置的 JSON Pointer),以及视情况而定的 value 或 from 字段。操作按顺序、原子地应用:任何一个失败,整个 patch 都会被拒绝。路径语法来自同月发表的姊妹文档 RFC 6901「JSON Pointer」,这是一份极小的字符串语法,由斜杠分隔的引用 token 组成(/user/address/city 意为「useraddresscity 键对应的值」;/items/3 意为「items 的第四个元素」,下标从零开始)。由于 /~ 是特殊字符,它们分别被转义为 ~1~0(按这个顺序,先解码 ~1,避免双重解码 bug)。JSON Patch 是 JSON diff 工具把结果喂给另一台机器时合适的输出格式。HTTP PATCH 方法(RFC 5789)正是为此类「部分更新」载荷而设计。Kubernetes 在自家的 strategic-merge-patch 之外也支持 RFC 6902。本工具目前不输出 JSON Patch,这是未来的功能方向,而非当前能力。

JSON Merge Patch(RFC 7396):更简单但有损的替代品

RFC 7396「JSON Merge Patch」由 Paul Hoffman 与 James Snell 于 2014 年 10 月发布。它完全不使用任何操作,patch 文档就是一份覆盖在原文档之上的「部分 JSON 对象」。出现在 patch 中的字段会覆盖原文档中对应字段;在 patch 中被设为 null 的字段会被删除;patch 中没出现的字段保持不变;数组整体被替换,从不合并。Merge Patch 简洁、易于手写。代价是表达力的损失:没办法把一个字段的值设为字面量 null(因为 null 在这里意味着「删除」);没办法只修改数组中的某一个元素(整个数组都会被替换);没办法表达 move 或 copy;没办法区分「我没碰这个字段」和「我想把它设为它当前的值」。对大多数日常 API 更新来说,这些限制可以接受,这也是 Merge Patch 在两种格式中更受欢迎的原因。但凡上述任一限制咬人的场景,RFC 6902 就胜出。Kubernetes 对两种格式都有意识地使用,按上下文挑合适的那一个。

可视化 diff 的渲染方式

展示一份结构 diff,常见有三种视觉约定。并排(Side-by-side):两列,原文在左,修改后在右,对应行对齐(本工具输入面板用的就是这种布局)。内联统一 diff(Inline unified diff):单列同时显示被删除行(通常以 - 起头、染红)、新增行(+、绿色)和未变上下文行(无前缀、普通文本),这是 git diff 的默认布局。树视图(Tree view):把 JSON 渲染成可折叠树,对变更节点高亮,未变化的节点折叠收起。生态里的颜色约定相当一致:绿表新增、红表删除、黄或琥珀表值变更、中性灰表未变。本工具完全遵循该约定:浅绿表新增、浅红表删除、浅黄表变更。大多数用户都能在 GitHub、GitLab、BitBucket、各种 IDE 的 diff 视图以及众多在线工具里见过这套颜色。

常见用途

值得知道的边界情形

数字精度。RFC 8259 说 JSON 数字是任意精度的,不限制位数。但 JavaScript 把每一个数字解析为 64 位 IEEE 754 双精度浮点。一旦超过 Number.MAX_SAFE_INTEGER(253 − 1 = 9,007,199,254,740,991),整数在「经过基于 JavaScript 的 diff」的来回往返时可能悄悄丢失精度。同样的问题在浮点上也存在,著名的 0.1 + 0.2 在 IEEE 754 下并不严格等于 0.3。严格 JSON 还禁止尾逗号与注释;按 RFC 8259 这两者都是语法错。JSON5(json5.org)是一个正式的超集,允许注释、尾逗号、单引号字符串、未加引号的键、十六进制数字、首末小数点、Infinity 与 NaN。JSONC 是微软在 VS Code 配置文件里使用的「带注释的 JSON」模式。本工具使用 JSON.parse(),因此两者都会被拒,粘贴前请先去掉注释与尾逗号。

日期字符串。JSON 没有原生日期类型。日期通常被编码成字符串,事实上的标准是 ISO 8601(具体到大多数互联网 API 所用的 RFC 3339 子集):YYYY-MM-DDTHH:MM:SS[.fff]Z。一个把日期当字符串比较的 diff,会把 2024-01-01T00:00:00Z2024-01-01T00:00:00.000Z 报告为不同(尽管它们代表的是同一瞬间)因为字符串本身就不同。重复键。RFC 8259 §4 写道「对象内的名字 SHOULD 唯一」,SHOULD 在规范层面上弱于 MUST。JavaScript 的 JSON.parse() 接受重复键并默默保留最后一个;其它 parser 可能保留第一个,或者干脆报错。null 与缺失。{"a": null}{} 是不同的 JSON 值。本工具把前者报告为「键 a 的值为 null」,把后者报告为「没有键 a」,它们被正确区分。JSON Merge Patch 把 null 当作「删除」信号来处理,本质上是在格式层面承认:这种区分既真实又棘手。

2026 年的生态

开源 JSON diff 由几家库主导。jsondiffpatch(Benjamin Eidelman,2012 年首发)是事实上的 JavaScript 库:GitHub ~5k 星,支持 objectHash、LCS 数组、反向 patch 和一个可视化 HTML 格式器。json-diff(Andrey Tarantsov)是 Node CLI 的经典工具,同名作品在 Python、Go、Rust 上都有平行实现。DeepDiff(Sep Dehpour)是 Python 主导库,递归 diff,提供「忽略顺序」「忽略数值类型变化」等众多细粒度选项。Linux Foundation 旗下 Jackson(Java)的 JsonNode diff 是 JVM 上的标准选择。jq 没有原生 diff 命令,但 =// 等运算符可以表达简单的结构比较。VS Code 内置 diff 以文本模式处理 JSON;JSON Tools 扩展则增加了对 JSON 友好的比较。像 Diffchecker 这类在线工具的 JSON 模式,本质上就是本工具所实现的同一种递归走树,常常是直接套在上述库之上。

为什么「只在浏览器里跑」在这里很重要

在服务器上 diff 两份 JSON 载荷意味着两份都要上传。对普通的公开数据样例,这无害。但当 API 响应里包含认证 token、客户 PII、内部员工记录、配置秘密或者未公开的产品数据时,就并非无害了。即便 diff 跑完,这些载荷仍会出现在服务器日志、可能的 CDN 缓存、可能的分析管线、可能的备份里。一个「只在浏览器里」的 diff 永远不发送,JSON 完全在你这页签里被解析与遍历。可以通过:点 Compare 时打开 DevTools 的 Network 选项卡核对,没有任何出站请求来验证。把 API 响应与一条基线比对、调试不同环境间的配置漂移、审计含有内部包 URL 的 lockfile 变化时,「JSON 永远不离开我设备」这一架构,正是让本工具可以放心用于真实生产数据的关键。

常见问题

键的顺序会影响比较结果吗?

不会。按规范 JSON 对象是无序的,所以 {"a":1, "b":2} 和 {"b":2, "a":1} 被视为相同。只有真正的值差异会被报告。

数组是如何比较的?

数组按索引比较。如果索引 2 处的数组元素在两个输入之间不同,会在该特定索引处报告差异。数组末尾的新增或删除项也会被检测到。

深度嵌套的对象会怎么处理?

比较完全递归。任意深度的嵌套对象和数组都会逐属性比较。输出会显示每个差异的完整路径(例如「user.address.city」)。

为什么我那超大整数的比较看起来不对?

JavaScript 把每一个 JSON 数字都解析成 64 位 IEEE 754 双精度。超过 Number.MAX_SAFE_INTEGER(253 − 1 = 9,007,199,254,740,991)的整数可能会悄悄丢失精度。因此一份包含 9007199254740993 的 JSON 文件可能会和一份包含 9007199254740992 的文件比对结果一致。这是底层数字表示的限制,不是 diff 工具的问题。如果你要 diff 的数据里包含 64 位 ID(Twitter snowflake ID、Discord ID、大的数据库主键),请让序列化器把它们以字符串而不是数字的形式输出。浮点比较有同一道告诫:著名的 0.1 + 0.2 在 IEEE 754 下并不严格等于 0.3

我能比较 JSON5 或 JSONC(带注释的 JSON)吗?

不能直接比。本工具用的是浏览器内置的 JSON.parse(),严格遵循 RFC 8259,注释、尾逗号、单引号字符串、未加引号的键都属于语法错误。如果你的输入是 JSON5(json5.org)或 VS Code 风格的 JSONC,请先去掉注释和尾逗号,或者用 jsonc-parser 这类工具先转成严格 JSON 再粘贴进来。

我的 JSON 会被发送到服务器吗?

不会。比对完全在你的浏览器里通过 JavaScript 运行。粘贴的 JSON 不会过网络,点 Compare 时打开 DevTools 的 Network 选项卡核对,或者载入页面后切到离线,diff 仍能工作。对于带认证 token 的 API 响应、含密钥的配置文件、含客户 PII 的数据库导出,都可以安全使用。

相关工具