JSON 对比:语义 diff 和行 diff 差在哪

· 约 3 分钟 JSON 对比

接口回归、配置审核、数据库快照对比——每次用 diff 命令比 JSON,十有八九被”假差异”淹没。原因很简单:JSON 有语义,文本 diff 没有。

两种 diff 的区别

文本 diff:按字符/行比,空格、换行、顺序任何一点不同都是差异。

语义 diff:先把两边解析成 JSON 树,再按规范比较——只看值和结构,忽略空白和书写顺序。

拿一组常见例子看:

A: {"name":"Alice","age":30}
B: {
     "age": 30,
     "name": "Alice"
   }
  • 文本 diff:从第 1 行开始全红全绿
  • 语义 diff:完全相等
A: {"tags":["a","b"]}
B: {"tags":["b","a"]}
  • 文本 diff:第 10 列字符不同
  • 语义 diff:数组顺序差异(如按集合比则相等)

哪些”差异”其实不是差异

1. key 顺序

JSON 规范:object 的 key 是无序集合。序列化库输出顺序依赖实现(Python 3.7+ 保留插入序、Go 的 map 是随机序)。

2. 空白、换行、缩进

只影响可读性,不影响语义。{"a":1}{\n "a": 1\n} 等价。

3. 字符串 Unicode 转义

"你好""你好" 是同一个字符串,JSON 规范允许两种写法。

4. 数值书写

1e2100100.0 在 JSON 数值语义下都是 100。但要注意精度1.01 在强类型语言(Go、Java)里可能不同。

5. 尾随零

{"price":10.00} 解析后变 10,再序列化出来没有 .00——这不是”差异”,是 IEEE 754 本身就不存尾随零。

哪些差异文本 diff 会漏

1. 数组顺序变化

A: [1,2,3]
B: [1,2,3]

看起来一样——但如果 B 实际是 [1,2,3](含 BOM)或多一个空元素 [1,2,3,](非法),文本 diff 可能显示行相同而解析失败。语义 diff 会直接拒绝非法 JSON。

2. 类型变更

A: {"id": 123}
B: {"id": "123"}

字符数几乎一样,文本 diff 只报 " 多了两个。语义 diff 会明确告诉你 number → string——这种变更往往是后端升级把大整数转成字符串,前端若直接做算术会报错。

3. 键的新增/删除 vs 键的重命名

A: {"userId": 1}
B: {"user_id": 1}

文本 diff 报一行变了。语义 diff 报”删除 userId、新增 user_id”——这是破坏性变更,接口契约完全断了。

JSON diff 的标准输出路径

好用的 JSON diff 工具输出会按JSONPath 定位到每个差异:

$.user.name          : "Alice" → "Bob"        (string)
$.items[2].price     : 9.9 → 10.0             (number)
$.tags               : ["x","y"] → ["x"]      (array shrank)
$.meta.createdAt     : added "2026-04-22"
$.meta.requestId     : removed

这种路径格式能:

  • 直接用 jq '.user.name' 验证
  • 复制到代码里当 key 访问
  • 作为忽略列表的表达式

业务里要不要”顺序敏感”

取决于数组的语义

场景数组性质建议
评论列表有序(按时间)顺序敏感
用户权限集合顺序不敏感
购物车有序顺序敏感
标签集合顺序不敏感
分页结果有序顺序敏感
枚举值列表集合顺序不敏感

工具不该替你决定,而该提供按路径配置的开关:$.permissions 用集合比,$.items 用顺序比。

接口契约对比的建议流程

  1. 抓两端响应,存成 a.json / b.json
  2. 格式化两边(消除空白差异)
  3. 忽略动态字段timestamprequestIdnonce
  4. 语义 diff,输出 JSONPath 差异列表
  5. 分类:类型变化、字段新增/删除、值变化
  6. 打回:类型变化和字段删除是破坏性变更,必须版本号升级或兼容处理

为什么不建议用 git diff 比 JSON

git diff 是行级文本 diff,遇到:

  • 压缩后的长 JSON 一行——全红全绿,完全看不出哪里变了
  • 格式化过的 JSON——和原始单行对比显示整文件重写
  • key 顺序变了——产生”假阳性”差异

正确做法:git 保留 JSON 的稳定格式(工具格式化后提交),对比用专用 JSON diff 工具,不用 git diff

粘进去直接对比

左右两侧贴 JSON,实时输出 JSONPath 级差异;支持忽略 key 顺序、按集合对比数组、屏蔽指定路径——不用来回跑 jqdiff

❓ 常见问题

两个 JSON 内容一样只是 key 顺序不同,算不算差异?

JSON 规范明确 object 的 key 是无序集合{"a":1,"b":2}{"b":2,"a":1} 语义完全相同。文本 diff 会报一堆红绿差异,语义 diff 会直接告诉你"相等"。所以对 JSON 必须用语义对比。

数组顺序呢?会不会忽略?

数组是有序的,[1,2] 不等于 [2,1],这是规范规定。但业务上很多数组其实是"集合"(权限列表、标签列表),这种情况需要工具提供"按集合比"的选项——否则接口方改了 push 顺序就会被误判成变更。

数字 1 和字符串 "1" 应该算相等吗?

JSON 里 1"1" 是两种类型,语义上不等。但后端传数据经常把大整数转字符串、前端格式化数字再发回,会造成"值相同但类型不同"。好的对比工具会显式标出"类型不同"而不是简单报"值变了"——两者处理方式完全不同。

能不能忽略某些字段再比?

日常对配置文件、接口快照时经常要这么做——忽略 timestamprequestIdtraceId 这种每次都变的字段。工具应提供"路径白名单/黑名单",比如 $.meta.timestamp 忽略、只比 $.data.*。正则或 JSONPath 表达式都常见。

打开 JSON 对比 语义 diff · 行级高亮 · 路径定位 · 忽略 key 顺序