JSON 差异对比
比较两个 JSON 对象并即时发现差异
用肉眼比较两个 JSON 文档,一旦超过几行就不可靠:键顺序被打乱、空白格式不同,都会把真正重要的改动藏起来。这个工具会解析两边、递归遍历,只报告真实差异,并为每一项给出路径。
比较的工作原理
两个输入都通过 JSON.parse 解析。每一边的顶层都必须是对象;根节点是数组或原始值会被拒绝。然后工具取每一层键的并集并逐一遍历。
- 如果某个键在两边都存在且两个值都是普通对象,就递归进入该对象,而不是把它当作叶子。
- 其余情况——原始值、数组、
null,以及只出现在一边的键——都作为叶子用深度相等比较。 - 缺失的键记为
undefined:只出现在左边的键是删除,只出现在右边的是新增,两边都存在但值不相等的是修改。 - 路径用点号连接,因此嵌套键会变成
address.city。 - 最后,所有条目按路径字母排序,并给出四个计数。
由于数组从不被递归进入,工具把整个数组当作一个值。两个数组只有在长度相同且每个元素都深度相等时才相等。
使用方法
- 粘贴原始 JSON 到左侧编辑器。页面预填了一个小示例,方便你立刻看到行为。
- 粘贴修改后的 JSON 到右侧编辑器。
- 点击对比。 不按按钮不会运行任何计算。
- 查看结果。 绿色表示新增,红色表示删除,黄色表示修改,灰色表示未修改。修改项会先显示带删除线的旧值,再显示新值。
任意一边 JSON 无效都会产生指明该侧的解析错误,因此多余的逗号或缺失的引号很容易定位。
实操示例:一个 API 响应
以页面初始的两个示例对象为例。
左侧:
{
"name": "Alice",
"age": 30,
"email": "alice@example.com",
"address": { "city": "New York", "zip": "10001" }
}
右侧:
{
"name": "Alice",
"age": 31,
"email": "alice@newdomain.com",
"address": { "city": "Brooklyn", "zip": "10001" },
"phone": "+1234567890"
}
工具会递归进入 address,并按路径排序报告以下条目:
| 路径 | 类型 | 详情 |
|---|---|---|
address.city | 已修改 | New York → Brooklyn |
address.zip | 未修改 | 10001 |
age | 已修改 | 30 → 31 |
email | 已修改 | alice@example.com → alice@newdomain.com |
name | 未修改 | Alice |
phone | 新增 | +1234567890 |
汇总显示 1 项新增、0 项删除、3 项修改、2 项未修改。注意 address 本身没有被列为变更——只有它变更的子键被列出,这使输出聚焦于真正不同的地方。
会标记什么,会跳过什么
| 情况 | 报告为 |
|---|---|
| 键只出现在右侧 | 新增 |
| 键只出现在左侧 | 删除 |
| 同键、不同的原始值 | 已修改 |
| 同键、不同类型(对象对字符串) | 已修改,并显示两个值 |
| 同键、深度相等的值 | 未修改 |
| 数组长度或元素不同 | 已修改(整个数组) |
| 数组元素相同且顺序一致 | 未修改 |
| 键顺序不同 | 无差异 |
| 重新缩进的输入 | 无差异 |
最后两行正是对 JSON 使用结构化差异而非文本差异的原因:格式噪音消失了。
为什么不直接用文本差异?
按行的文本差异比较字符,因此每次键移动或缩进变化都会报告为改动。JSON 的键顺序没有意义,格式化工具又会自由改变空白,所以对两个语义相同的文档做文本差异,可能让整个文件都高亮。这个工具先解析再按值比较,从而去掉噪音。它在数组上做了相反的取舍:文本差异能显示插入了哪些行,而这个工具把整个数组报告为一个变更值。结构重要时用 JSON 差异,精确的字符编辑重要时用文本差异。
数组不够用时怎么办
因为数组是全有或全无,在大数组开头插入一个元素,会被报告为单条 changed 条目,展示整个旧数组和新数组。工具不会告诉你插入了 items[0],也不会告诉你 items[3].id 发生了变化。对小型数组这没问题,而且当元素没有稳定标识时,它避免了含糊的对齐。如果你需要元素级比较,请先规范化数据——例如把数组转成以 id 为键的对象——再对规范化后的形式做差异。
阅读输出与计数
四个计数器包含未修改的叶子,因此它们之和等于比较过的叶子总数,而不是差异数量。没有任何差异时,页面会显示简短的”无差异”信息,并仍然展示全部四个计数器。由于未修改条目也包含在列表里,一个大对象即使只有一个值变了也可能产生很长的结果;如果你只关心变动,浏览新增、删除、修改三种彩色标记即可。所有处理都在浏览器本地完成,API 响应和配置文件永远不会被传输。
相关工具
- JSON 格式化工具 — 验证、格式化并美化原始 JSON 字符串为可读的缩进结构。
- 差异检查器 — 并排比较两段纯文本,高亮显示差异。
- JSON 转 CSV — 将 JSON 数组转换为逗号分隔值,方便电子表格分析。
- YAML 转 JSON 转换器 — 在 YAML 和 JSON 格式之间相互转换。
常见问题
为什么这个工具要求顶层必须是对象?
差异比对基于具名键和 `address.city` 这样的点号路径构建,因此要求两个输入的根节点都是 JSON 对象。顶层是纯数组、字符串、数字或 `null` 都会被拒绝,并给出明确的错误提示。如果需要比较数组,请把它包进对象里,例如 `{ "items": [...] }`。
数组元素是如何比较的?
数组被当作单个叶子值,用深度相等来判断。只要长度不同,或任意深度上有任一元素不同,整个数组都会报告为"已修改",并并排展示旧值和新值。工具不会报告 `items[2]` 这样的具体下标;元素级差异没有实现。
差异条目按什么顺序排列?
递归遍历结束后,路径会按语言环境感知的字符串规则做字母排序。这使输出是确定的,同样的输入永远得到同样的顺序。这也意味着 `address.city` 会排在 `age` 前面,因为比较是逐字符进行的,而不是按嵌套深度。
哪些内容计入"未修改"数量?
凡是两边都存在且深度相等的叶子都会计为未修改,包括嵌套键。当两边都是对象时,工具会递归进入,父路径本身不计入,只有叶子计数。新增的键、删除的键或修改过的值都不算未修改。
工具如何处理 null 与缺失键?
显式为 `null` 的键是存在的,会作为值参与比较;缺失的键则是 undefined。因此把字段从 `null` 改成字符串会报告为已修改,而新增一个恰好为 `null` 的字段会报告为新增。当 API 用 null 表示"无值"而不是省略字段时,这个区别很重要。
键的顺序或空白会影响结果吗?
不会。两个输入都用 JSON.parse 解析后按值比较,所以重新排列键顺序或改变缩进格式都不会产生差异。只有值、类型和存在性的真实变化才会被报告,这正是它适合处理版本控制里噪音差异的原因。
由 HandyTools Hub 团队审校 · 最后更新