发布于2026-07-23 阅读(0)
扫一扫,手机访问
说起来,工作中经常需要比对两组JSON数据,然后返回差异结果。调研了一圈,发现几个不错的工具,这里整理一下,按场景选型就行了。

JSONassert 这玩意儿,打一开始就是为测试场景量身定做的。它支持忽略字段顺序、数组顺序、数值精度这些宽松模式,遇到差异直接抛异常,而且异常信息里会清清楚楚地告诉你,是哪个路径下的哪个值出了问题。
org.skyscreamer jsonassert 1.5.1 test
import org.skyscreamer.jsonassert.JSONAssert;
import org.skyscreamer.jsonassert.JSONCompareMode;
public class JsonAssertDemo {
public static void main(String[] args) {
// 待比对的两个JSON字符串(结构相同,值有差异)
String expectedJson = "{"name":"张三","age":20,"address":{"city":"北京"},"hobbies":["篮球","游泳"]}";
String actualJson = "{"name":"李四","age":20,"address":{"city":"上海"},"hobbies":["篮球","跑步"]}";
try {
// 严格模式(字段顺序、数组顺序、值完全一致才通过)
JSONAssert.assertEquals(expectedJson, actualJson, JSONCompareMode.STRICT);
} catch (AssertionError e) {
// 捕获差异并输出
System.out.println("JSON差异:n" + e.getMessage());
}
}
}
JSON差异:
Expected: "张三"
got: "李四"
at path $["name"]
Expected: "北京"
got: "上海"
at path $["address"]["city"]
Expected: "游泳"
got: "跑步"
at path $["hobbies"][1]
JsonUnit 是在 JSONassert 和 Jackson 基础上封装的一层,API 设计得更加简洁,差异输出也更易读。它支持 assertThat 风格的断言,写起测试来很顺手,适合 BDD 测试风格。
net.ja vacrumbs.json-unit json-unit-core 2.38.0 test com.fasterxml.jackson.core jackson-databind 2.15.2
import static net.ja vacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
public class JsonUnitDemo {
public static void main(String[] args) {
String expectedJson = "{"name":"张三","age":20,"address":{"city":"北京"}}";
String actualJson = "{"name":"李四","age":21,"address":{"city":"北京"}}";
// 比对并输出差异(AssertJ风格)
assertThatJson(actualJson)
.isEqualTo(expectedJson)
.onFailure(failure -> System.out.println("JSON差异:n" + failure.getMessage()));
}
}
JSON差异:
Expected value <"张三"> but was <"李四"> at path $['name']
Expected value <20> but was <21> at path $['age']
// 忽略age字段的差异
assertThatJson(actualJson)
.whenIgnoringPaths("age")
.isEqualTo(expectedJson);
Jackson 是 Ja va 生态里最主流的 JSON 处理库,没有之一。通过它的 JsonNode 树模型,你可以完全自定义比对逻辑,这在业务开发中需要精细控制差异的场景下特别有用。
com.fasterxml.jackson.core jackson-databind 2.15.2
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import ja va.util.ArrayList;
import ja va.util.List;
public class JacksonJsonDiff {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final List DIFFS = new ArrayList<>();
// 比对两个JSON字符串,返回差异列表
public static List compareJson(String expectedJson, String actualJson) throws Exception {
DIFFS.clear();
JsonNode expectedNode = OBJECT_MAPPER.readTree(expectedJson);
JsonNode actualNode = OBJECT_MAPPER.readTree(actualJson);
compareNodes("", expectedNode, actualNode);
return DIFFS;
}
// 递归比对JsonNode节点
private static void compareNodes(String path, JsonNode expected, JsonNode actual) {
// 节点类型不同
if (expected.getNodeType() != actual.getNodeType()) {
DIFFS.add(path + ":类型不一致(预期:" + expected.getNodeType() + ",实际:" + actual.getNodeType() + ")");
return;
}
// 叶子节点(值节点)
if (expected.isValueNode()) {
if (!expected.equals(actual)) {
DIFFS.add(path + ":值不一致(预期:" + expected.asText() + ",实际:" + actual.asText() + ")");
}
return;
}
// 对象节点(递归比对子字段)
if (expected.isObject()) {
expected.fieldNames().forEachRemaining(fieldName -> {
String newPath = path.isEmpty() ? "$." + fieldName : path + "." + fieldName;
JsonNode expectedChild = expected.get(fieldName);
JsonNode actualChild = actual.get(fieldName);
if (actualChild == null) {
DIFFS.add(newPath + ":实际JSON缺失该字段");
} else {
compareNodes(newPath, expectedChild, actualChild);
}
});
// 检查实际JSON是否有预期外的字段
actual.fieldNames().forEachRemaining(fieldName -> {
if (!expected.has(fieldName)) {
String newPath = path.isEmpty() ? "$." + fieldName : path + "." + fieldName;
DIFFS.add(newPath + ":实际JSON包含预期外的字段");
}
});
}
// 数组节点(递归比对数组元素)
if (expected.isArray()) {
int expectedSize = expected.size();
int actualSize = actual.size();
if (expectedSize != actualSize) {
DIFFS.add(path + ":数组长度不一致(预期:" + expectedSize + ",实际:" + actualSize + ")");
}
// 比对数组元素(按索引)
int maxSize = Math.max(expectedSize, actualSize);
for (int i = 0; i < maxSize; i++) {
String newPath = path + "[" + i + "]";
JsonNode expectedChild = i < expectedSize ? expected.get(i) : null;
JsonNode actualChild = i < actualSize ? actual.get(i) : null;
if (expectedChild == null) {
DIFFS.add(newPath + ":实际数组多出元素");
} else if (actualChild == null) {
DIFFS.add(newPath + ":实际数组缺失元素");
} else {
compareNodes(newPath, expectedChild, actualChild);
}
}
}
}
// 测试
public static void main(String[] args) throws Exception {
String expectedJson = "{"name":"张三","age":20,"address":{"city":"北京"},"hobbies":["篮球","游泳"]}";
String actualJson = "{"name":"李四","age":20,"address":{"city":"上海"},"hobbies":["篮球","跑步"]}";
List diffs = compareJson(expectedJson, actualJson);
System.out.println("JSON差异列表:");
diffs.forEach(diff -> System.out.println("- " + diff));
}
}
JSON差异列表:
- $.name:值不一致(预期:张三,实际:李四)
- $.address.city:值不一致(预期:北京,实际:上海)
- $.hobbies[1]:值不一致(预期:游泳,实际:跑步)
Diffuse 是一个轻量级工具,它的特色是能输出 JSON 格式的结构化差异报告,把添加、修改、删除的节点都给你列出来。如果你需要把差异持久化或者传输给其他系统,这个工具就很合适。
me.snov diffuse 0.2.0
import me.snov.diffuse.Diffuse;
import me.snov.diffuse.JsonDiff;
public class DiffuseDemo {
public static void main(String[] args) {
String expectedJson = "{"name":"张三","age":20}";
String actualJson = "{"name":"李四","age":21,"gender":"男"}";
// 生成结构化差异报告
JsonDiff diff = Diffuse.diff(expectedJson, actualJson);
System.out.println("结构化差异报告:n" + diff.toJson());
}
}
{
"changed": [
{
"path": "/name",
"from": "张三",
"to": "李四"
},
{
"path": "/age",
"from": 20,
"to": 21
}
],
"added": [
{
"path": "/gender",
"value": "男"
}
],
"removed": []
}
总结一下,选型其实不复杂:
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8