Java JSON比对四大方案 JSONassert、JsonUnit、Jackson、Diffuse实战选型

日常 Java 开发中经常需要比对前后两组 JSON 数据,精准识别字段差异,以此校验数据一致性。直接对比字符串极易受格式、空格干扰,难以定位差异路径。本文梳理 Java 生态四种主流 JSON 比对方案,分别介绍 JSONassert、JsonUnit、Jackson、Diffuse 的特性、代码示例与适用场景,结合业务与测试场景给出清晰选型参考,方便开发者按需选用。
核心工具对比
| 工具 | 核心特点 | 适用场景 |
|---|---|---|
| JSONassert | 专注 JSON 断言,差异信息精准,支持宽松/严格模式 | 单元测试(JUnit/TestNG) |
| JsonUnit | 封装 JSONassert+Jackson,API 更友好,易读性高 | 测试场景(推荐) |
| Jackson | 灵活的 JSON 树模型,可完全自定义比对逻辑 | 业务开发(需自定义规则) |
| Diffuse | 轻量级,输出结构化 JSON 差异报告 | 需要结构化差异数据的场景 |
1. JSONassert(测试场景首选)
JSONassert 是专为测试设计的 JSON 比对工具,支持 忽略字段顺序、数组顺序、数值精度 等宽松模式,差异会以异常形式抛出,包含具体路径和值的对比。
步骤 1:引入依赖(Maven)
<dependency>
<groupId>org.skyscreamer</groupId>
<artifactId>jsonassert</artifactId>
<version>1.5.1</version>
<scope>test</scope> <!-- 测试场景建议 test scope -->
</dependency>
步骤 2:实操示例(比对差异并输出)
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]
关键配置(JSONCompareMode)
STRICT:严格模式(字段顺序、数组顺序、值必须完全一致)LENIENT:宽松模式(忽略字段顺序,数组顺序仍校验)NON_EXTENSIBLE:不允许实际 JSON 包含预期外的字段IGNORE_ARRAY_ORDER:忽略数组元素顺序IGNORE_VALUES:只校验结构,忽略值的差异
2. JsonUnit(更友好的测试 API)
JsonUnit 基于 JSONassert 和 Jackson 封装,API 更简洁,差异输出更易读,支持 assertThat 风格的断言(符合 BDD 测试习惯)。
步骤 1:引入依赖
<dependency>
<groupId>net.javacrumbs.json-unit</groupId>
<artifactId>json-unit-core</artifactId>
<version>2.38.0</version>
<scope>test</scope>
</dependency>
<!-- 依赖 Jackson(JsonUnit 底层用 Jackson 解析) -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
步骤 2:实操示例
import static net.javacrumbs.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);
3. Jackson(业务开发灵活定制)
Jackson 是 Java 最主流的 JSON 处理库,通过 JsonNode 树模型可完全自定义比对逻辑(适合业务开发中需要精细控制差异的场景)。
步骤 1:引入依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
步骤 2:自定义比对工具类(输出差异路径和值)
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.ArrayList;
import java.util.List;
public class JacksonJsonDiff {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final List<String> DIFFS = new ArrayList<>();
// 比对两个 JSON 字符串,返回差异列表
public static List<String> 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<String> diffs = compareJson(expectedJson, actualJson);
System.out.println("JSON 差异列表:");
diffs.forEach(diff -> System.out.println("- " + diff));
}
}
输出结果
JSON 差异列表: - $.name:值不一致(预期:张三,实际:李四) - $.address.city:值不一致(预期:北京,实际:上海) - $.hobbies[1]:值不一致(预期:游泳,实际:跑步)
4. Diffuse(轻量级结构化差异输出)
Diffuse 是轻量级工具,可输出 JSON 格式的结构化差异报告(包含添加 / 修改 / 删除的节点),适合需要将差异持久化或传输的场景。
步骤 1:引入依赖
<dependency>
<groupId>me.snov</groupId>
<artifactId>diffuse</artifactId>
<version>0.2.0</version>
</dependency>
步骤 2:实操示例
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());
}
}
输出结果(JSON 格式)
{
"changed": [
{
"path": "/name",
"from": "张三",
"to": "李四"
},
{
"path": "/age",
"from": 20,
"to": 21
}
],
"added": [
{
"path": "/gender",
"value": "男"
}
],
"removed": []
}
选型建议
- 测试场景:优先选 JsonUnit(API 友好)或 JSONassert(轻量);
- 业务开发:优先选 Jackson(灵活自定义比对规则);
- 需要结构化差异输出:选 Diffuse;
- 特殊需求(如忽略数值精度、时间格式):基于 Jackson 扩展自定义比对逻辑。
综上,四款 JSON 比对工具各有侧重,不存在通用最优方案。测试场景优先选择 JsonUnit 或 JSONassert 快速完成断言校验;业务开发追求高度自定义逻辑可采用 Jackson 递归实现;需要结构化差异报文输出则使用 Diffuse。开发时结合项目场景、扩展需求综合考量,选择适配方案高效完成 JSON 数据比对工作。
以上关于Java JSON比对四大方案 JSONassert、JsonUnit、Jackson、Diffuse实战选型的文章就介绍到这了,更多相关内容请搜索码云笔记以前的文章或继续浏览下面的相关文章,希望大家以后多多支持码云笔记。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 admin@mybj123.com 进行投诉反馈,一经查实,立即处理!
重要:如软件存在付费、会员、充值等,均属软件开发者或所属公司行为,与本站无关,网友需自行判断
码云笔记 » Java JSON比对四大方案 JSONassert、JsonUnit、Jackson、Diffuse实战选型
微信
支付宝