一、为什么文本 diff 不够用
文本比对(diff、编辑器的 compare)逐行比较字符,遇到 JSON 会同时被三件事干扰:
- 键顺序不同:
{"a":1,"b":2}与{"b":2,"a":1}语义完全相同,文本 diff 却判定整行都变了。 - 空白与换行:压缩版和美化版比,等于两篇完全不同的文章。
- 转义差异:
"中"和"\u4e2d"是同一个值。
所以真正有用的 JSON diff 必须先解析成对象树,再递归比较结构。
二、递归比较的四种结果
对每个键,结果只有四类,输出时按类着色最清晰:
- 新增:只在右边存在。
- 删除:只在左边存在。
- 修改:两边都有但值不同(再区分类型变化,如
"1"→1)。 - 相同:可折叠隐藏。
路径用 JSON Pointer 风格表达最精确,例如 /data/list/3/name,比「第 4 个元素的 name」更不容易歧义。
三、数组是最麻烦的部分
数组没有键,只能靠位置或业务主键对齐:
| 对齐方式 | 适用场景 | 风险 |
|---|---|---|
| 按索引 | 顺序稳定、元素语义相同 | 中间插入一个元素,后面全部错位 |
| 按主键(如 id) | 列表型数据 | 需要指定用哪个字段做主键 |
| 按值集合 | 顺序无关的标签类数组 | 重复值无法区分 |
实践中默认按索引比较,但列表类数据一定要能切换成按主键,否则一次「插入了一条」会被报成「后面 50 条全改了」。
四、几个必须明确的语义
null与键缺失:{"a":null}和{}在多数语言里取出来都是空,但语义不同。工具应区分显示。- 数字精度:
1与1.0在 JSON 里是同一个数,比较时应视为相等;但0.1+0.2这类浮点误差要允许容差。 - 大小写与类型:
"true"和true必须算不同,否则前端判断会出错。 - 数组顺序:默认顺序敏感;如果业务上无序,要显式开启忽略顺序。
五、实操流程
- 两边都先格式化成同一缩进,排除空白干扰。
- 解析失败先修语法,不能带错比较。
- 比较结果先看「新增/删除」的顶层键,再看深层修改。
- 对差异逐条确认是「预期变更」还是「环境差异」——后者常见于时间戳、自增 ID、服务器地址。
- 把差异结论写进变更记录,而不是截图丢群里。
调试接口时,用 JSON 比对工具 左右贴入两段内容即可高亮差异;单段内容有问题时先用 JSON 格式化校验 定位语法错误。两者都在浏览器本地处理,生产数据不用上传。
小结
JSON diff 的正确姿势是「解析成树 + 递归比较 + 明确数组对齐方式 + 区分 null 与缺失」。键顺序和空白不该产生差异,而类型变化必须产生差异。