JSON 结构化对比
按结构比而不是按行比。键顺序换了、缩进改了都不会误报,只有真正变了的键和值才会出现,并且带上能直接定位的路径。
对比选项
对象键顺序在 JSON 里本来就不携带含义,所以无论开关怎么设都不会误报。三个开关只影响值的比较方式,用来把机械差异滤掉、留下业务上真正关心的改动。
对比结果
7 处差异
共发现 7 处差异:新增 2 处,修改 4 处,类型变化 1 处。
差异明细
| 路径 | 类型 | 变更前 | 变更后 | 说明 |
|---|---|---|---|---|
| $.version | 修改 | "1.2.0" | "1.3.0" | 由 "1.2.0" 改为 "1.3.0" |
| $.replicas | 类型变化 | 2 | "2" | 类型由 number 变成 string |
| $.env.REGION | 新增 | (不存在) | "cn-hangzhou" | 新增 "cn-hangzhou" |
| $.env.LOG_LEVEL | 修改 | "info" | "debug" | 由 "info" 改为 "debug" |
| $.ports[2] | 新增 | (不存在) | 9100 | 新增 9100 |
| $.tags[0] | 修改 | "api" | "stable" | 由 "api" 改为 "stable" |
| $.tags[1] | 修改 | "stable" | "api" | 由 "stable" 改为 "api" |
可复制的差异清单
每行格式是「标记 路径 说明」,+ 表示新增、- 表示删除、~ 表示修改、! 表示类型变化。可以直接贴进工单或提交信息里。
归一化输出
把两份 JSON 的键排序、缩进统一后输出。如果归一化后完全一致,说明差异只来自格式而不是内容;把归一化结果存回文件,下次对比就不会再被键顺序和缩进干扰。
为什么不用文本行对比
文本对比看的是行。把缩进从 2 个空格改成 4 个,或者用工具重新格式化了整个文件,文本对比会满屏标红,但 JSON 的语义一点没变。反过来,如果两个人改了同一个键的值,文本对比只告诉你「这一行变了」,不会告诉你路径是什么。JSON 是有结构的,按树比能得到精确得多的答案。
键顺序同理。{"a":1,"b":2} 和 {"b":2,"a":1} 在 RFC 8259 里是完全相同的 JSON 值,序列化时键的顺序是实现细节。所以这个工具默认按集合比较键,顺序不同不报差异——这也正是它比文本对比更适合核对配置文件和接口响应的原因。
对比结果怎么读
路径用的是 JSONPath 的常用子集:根是 $,对象成员用点号连接,非标识符的键写成方括号加引号($["user-name"]),数组用下标($.list[0])。复制出来可以直接粘进 jq、Grafana 或者各种 JSONPath 工具继续用,不用再手工数层级。
四类差异分别是:新增指右边的文档多了一个键或数组元素,删除指左边有而右边没有,修改指同一位置的标量值变了,类型变化指这个位置整体换了种类(比如原来是对象,现在是数组)。会把类型变化单独拎出来,是因为这种情况在文本对比里往往只表现为一小段改动,但它对程序来说是最容易出问题的改动。
三个开关分别在什么时候用
忽略字符串大小写适合比较状态字段。RUNNING 和 running 在多数系统里是同一个状态,只是序列化风格不同,这类噪声滤掉能让你看清真正的改动。数字与字符串视为相同对接口回归测试特别有用,因为同一个字段有时返回 1 有时返回 "1",做功能验证时往往可以放过;但如果你是在校对接口契约,建议关掉,因为类型不一致本身就该被暴露。
忽略数组顺序的用法更微妙。数组在 JSON 里默认是有序的,[1,2] 和 [2,1] 是两份不同的数据,所以默认按位置逐个比。但核对用户清单、权限列表这类数据时,顺序常常只是查询没带排序造成的噪声。开启后工具会先找出内容一致的项,再对剩余项按字段重合度配对,这样「某条记录的某个字段改了」会报成一条精确的修改,而不是含糊的删除加新增。
5 个容易踩的点
JSON 对象的键顺序没有含义。RFC 8259 明确说对象是无序的键值对集合,所以 {"a":1,"b":2} 和 {"b":2,"a":1} 在语义上完全相同。这个工具默认就按集合来比,键顺序不同不会报差异;反过来,如果两份文件只是格式化缩进不同,这里也不会出现任何红绿,这正是它比文本行对比更适合看配置和接口的地方。
数组顺序默认是有含义的。数组和对象不同,[1,2] 和 [2,1] 是两份不同的数据,所以默认按位置逐个比。但对比「用户清单」「权限列表」这类数据时,顺序往往只是查询没排序造成的噪声,这时打开「忽略数组顺序」会先找出内容一致的项,再对剩余项按字段重合度配对,从而把结果收敛成「第 3 个用户的角色从 viewer 变成了 admin」这种能直接读懂的结论。
解析阶段会丢掉重复键。JSON.parse 遇到重复键时保留最后一个,所以 {"a":1,"a":2} 解析后只剩 a 为 2,这一点无法在解析后再补救。如果怀疑输入有重复键,用文本对比或者专门的 JSON 检查工具先看一眼更稳妥。同理,超大的整数(超过 2 的 53 次方)在 JavaScript 里会丢精度,订单号这类长数字建议在接口里就以字符串传。
数字和字符串混用是接口的常见毛病。同一个字段有时返回 1 有时返回 "1",常出现在后端语言序列化行为不一致或者缓存里存的类型不同。打开「忽略数字与字符串的区别」后,这类差异会被视为相同,同时 true 和 "true" 也会归一。做回归测试对比两次响应时这个开关很有用,但正式校验接口契约时建议关掉,因为类型不一致本身就该被暴露出来。
路径的写法。根用 $ 表示,对象成员用点号连接($.user.name),非标识符的键用方括号加引号($["user-name"]),数组用下标($.list[0])。这套写法就是 JSONPath 的常用子集,复制出来可以直接粘进 jq、JSONPath 测试工具或者 Grafana 之类的地方继续用。