如何比较两个 JSON 文件的差异
← 返回博客
如何比较两个 JSON 文件的差异
· 5 分钟阅读
JSON 比较的核心难点
JSON 文件的比较比普通文本文件更复杂,原因在于 JSON 的语义等价性不等同于字符串等价性。两个 JSON 对象即使字段顺序不同,在语义上可能完全等价({"a":1,"b":2} 和 {"b":2,"a":1} 是同一个 JSON 对象)。但如果用普通的文本 diff 工具比较,它们会被认为是不同的——这会产生大量误报的"差异"。
同样,格式化方式不同的 JSON(缩进、换行、空格)在文本层面不同,但语义完全相同。这就是为什么需要专门的 JSON-aware diff 工具,而不是简单地用 diff 命令对比两个文件。理解这个区别,是选择正确比较方法的前提。
使用 jq 进行命令行对比
jq 是命令行 JSON 比较最强大的工具。结合 jq -S(对键名排序)和标准 diff,可以实现忽略字段顺序的语义比较:
# 基本语义比较(忽略字段顺序和格式差异)
# Semantic comparison (ignores field order and formatting differences)
diff /dev/null; then
echo "JSONs are identical"
else
echo "JSONs differ"
fi
JavaScript 中的 JSON 深比较
在 JavaScript/Node.js 中,直接用 === 比较两个对象只比较引用,不比较内容。需要深度比较(deep equal)来确认两个 JSON 值是否语义等价:
// 使用 JSON.stringify + 排序键(简单场景)
// Using JSON.stringify + sorted keys (simple scenarios)
function jsonEqual(a, b) {
return JSON.stringify(sortKeys(a)) === JSON.stringify(sortKeys(b));
}
function sortKeys(obj) {
if (Array.isArray(obj)) return obj.map(sortKeys);
if (obj && typeof obj === 'object') {
return Object.fromEntries(
Object.entries(obj).sort().map(([k, v]) => [k, sortKeys(v)])
);
}
return obj;
}
// 使用 fast-deep-equal 库(推荐,处理边缘情况)
// Using fast-deep-equal library (recommended, handles edge cases)
import equal from 'fast-deep-equal';
console.log(equal(obj1, obj2)); // true/false
// 使用 deep-diff 库获取详细差异
// Using deep-diff library for detailed differences
import { diff } from 'deep-diff';
const differences = diff(obj1, obj2);
// differences 是变更数组,每个元素包含路径、类型、旧值、新值
Python 中的 JSON 比较
Python 比较 JSON 非常简洁,因为 Python 的字典比较天然忽略键的顺序:
import json
# 加载并比较(Python dict 比较忽略键顺序)
# Load and compare (Python dict comparison ignores key order)
with open('file1.json') as f:
obj1 = json.load(f)
with open('file2.json') as f:
obj2 = json.load(f)
print(obj1 == obj2) # True if semantically equal
# 获取详细差异
# Get detailed differences
def json_diff(obj1, obj2, path=""):
if type(obj1) != type(obj2):
print(f"{path}: type changed {type(obj1).__name__} → {type(obj2).__name__}")
return
if isinstance(obj1, dict):
for key in set(obj1) | set(obj2):
if key not in obj1:
print(f"{path}.{key}: added = {obj2[key]}")
elif key not in obj2:
print(f"{path}.{key}: removed = {obj1[key]}")
else:
json_diff(obj1[key], obj2[key], f"{path}.{key}")
elif isinstance(obj1, list):
for i, (a, b) in enumerate(zip(obj1, obj2)):
json_diff(a, b, f"{path}[{i}]")
if len(obj1) != len(obj2):
print(f"{path}: length changed {len(obj1)} → {len(obj2)}")
elif obj1 != obj2:
print(f"{path}: {obj1!r} → {obj2!r}")
API 响应比较:测试中的用法
在 API 测试中,比较响应 JSON 是一项常见需求:验证接口在重构前后行为一致,或者对比不同环境(测试/生产)的响应差异。通常需要忽略动态字段(如时间戳、请求 ID)后再比较:
# 使用 jq 忽略动态字段后比较两个 API 响应快照
# Using jq to ignore dynamic fields before comparing two API response snapshots
IGNORE_FIELDS='del(.meta.timestamp, .meta.requestId, .data.updatedAt)'
diff \
/tmp/old.json
git show HEAD:config.json | jq -S . > /tmp/new.json
diff /tmp/old.json /tmp/new.json
JSON Patch:描述 JSON 差异的标准格式
RFC 6902 定义了 JSON Patch 格式,用于描述对 JSON 文档进行的一系列操作(add、remove、replace、move、copy、test)。这不仅是一种表示"差异"的标准方式,还可以作为 API 的 PATCH 请求格式,精确描述客户端希望对资源做的修改:
在需要精确追踪和回放 JSON 变更历史的场景(如协同编辑、配置变更审计),JSON Patch 比简单的"前后对比"更有用:你可以将一系列 Patch 操作逐步应用,实现精细的版本控制和变更回滚。配合 RFC 7396 的 JSON Merge Patch(更简单的部分更新格式),可以满足大多数 API PATCH 操作的需求。
// JSON Patch 示例 / JSON Patch example
[
{ "op": "replace", "path": "/user/name", "value": "Bob" },
{ "op": "add", "path": "/user/phone", "value": "+1-555-0100" },
{ "op": "remove", "path": "/user/legacy_field" }
]
// 使用 fast-json-patch 库应用 Patch
import jsonpatch from 'fast-json-patch';
const patched = jsonpatch.applyPatch(originalDoc, patchOps).newDocument;
// 生成两个对象之间的 Patch
const patch = jsonpatch.compare(originalDoc, modifiedDoc);
立即尝试在线工具,无需安装,免费使用。
打开工具 →
立即免费使用相关工具
免费使用 →