如何在Java中忽略JSON数组顺序比较两个JSON响应?
通用API JSON响应对比方案(支持无序数组、值与Schema校验)
核心需求回顾
需要实现通用的JSON响应对比,满足:
- 检测值不匹配与Schema不匹配(如字段缺失/冗余、类型不一致)
- 数组元素内容相同但顺序不同时,判定为无差异
- 无需预定义Java对象,适配任意JSON结构
可行方案
1. 使用支持无序数组对比的现成工具
推荐两个开箱即用的Java库,无需自定义复杂逻辑:
JSONAssert
支持多种对比模式,可直接配置忽略数组顺序,同时严格校验Schema(字段数量、类型一致):
import org.skyscreamer.jsonassert.JSONAssert; import org.skyscreamer.jsonassert.JSONCompareMode; public class JsonComparisonUtil { public static void compareJson(String expectedJson, String actualJson) throws Exception { // STRICT模式:校验Schema(字段不能多/少、类型一致),第二个参数开启无序数组对比 JSONCompareMode mode = new JSONCompareMode(JSONCompareMode.STRICT, true); JSONAssert.assertEquals(expectedJson, actualJson, mode); } }
- 当数组顺序不同但元素一致时,不会判定为差异
- 自动检测Schema问题:比如预期有
skillset字段但实际没有,或字段类型不匹配(如预期是数组实际是字符串) - 无需预定义任何Java实体类,直接传入JSON字符串即可
JsonUnit
同样支持忽略数组顺序,且提供更灵活的断言API:
import static net.javacrumbs.jsonunit.JsonAssert.assertJsonEquals; import static net.javacrumbs.jsonunit.core.Option.IGNORING_ARRAY_ORDER; public class JsonComparisonUtil { public static void compareJson(String expectedJson, String actualJson) { assertJsonEquals(expectedJson, actualJson, IGNORING_ARRAY_ORDER); } }
- 支持Schema校验(默认严格检查字段存在性)
- 可扩展自定义匹配规则(比如忽略某些字段、模糊匹配数值)
2. 适配现有工具(如zjsonpatch)+ 数组预排序
如果需要保留RFC 6902格式的diff输出,可以对JSON中的数组先做排序预处理,再用原工具对比:
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.flipkart.zjsonpatch.JsonDiff; import java.util.Comparator; public class JsonArraySortAndCompare { private static final ObjectMapper mapper = new ObjectMapper(); // 递归遍历JSON,对所有数组按元素序列化后的字符串排序 private static JsonNode sortAllArrays(JsonNode node) { if (node.isArray()) { ArrayNode sortedArray = mapper.createArrayNode(); // 递归处理每个数组元素,再排序 node.elements().forEachRemaining(elem -> sortedArray.add(sortAllArrays(elem))); return sortedArray.sort(Comparator.comparing(JsonNode::toString)); } else if (node.isObject()) { node.fields().forEachRemaining(entry -> { ((com.fasterxml.jackson.databind.node.ObjectNode) node) .set(entry.getKey(), sortAllArrays(entry.getValue())); }); return node; } // 基本类型直接返回 return node; } public static String compareJson(String json1, String json2) throws Exception { JsonNode sortedNode1 = sortAllArrays(mapper.readTree(json1)); JsonNode sortedNode2 = sortAllArrays(mapper.readTree(json2)); // 生成RFC 6902格式的diff,空数组表示无差异 return JsonDiff.asJson(sortedNode1, sortedNode2).toString(); } }
- 预处理后,顺序不同的数组会被转换成结构一致的JSON,zjsonpatch将不再判定为差异
- 同时保留原工具的Schema和值差异检测能力
3. 自定义通用对比逻辑(灵活扩展)
如果需要完全自定义对比规则,可以实现递归遍历JSON节点的逻辑:
- 对象节点:对比所有键的集合是否一致(Schema校验),再递归对比每个键对应的值
- 数组节点:不按索引匹配,采用双向匹配策略:
- 遍历第一个数组的每个元素,在第二个数组中找到匹配的元素(递归对比内容)
- 每个元素只能匹配一次,处理重复元素的情况
- 基本类型节点:直接对比值是否相等
关键代码片段示例:
import com.fasterxml.jackson.databind.JsonNode; import java.util.ArrayList; import java.util.List; public class CustomJsonComparator { public static boolean compare(JsonNode a, JsonNode b) { if (a.getNodeType() != b.getNodeType()) { return false; // Schema不匹配:类型不同 } switch (a.getNodeType()) { case OBJECT: // 对比键集合是否一致 if (!a.fieldNames().nextAll().equals(b.fieldNames().nextAll())) { return false; } // 递归对比每个字段的值 for (String key : a.fieldNames()) { if (!compare(a.get(key), b.get(key))) { return false; } } return true; case ARRAY: if (a.size() != b.size()) { return false; } // 复制数组元素,用于双向匹配 List<JsonNode> bElements = new ArrayList<>(); b.elements().forEachRemaining(bElements::add); for (JsonNode aElem : a) { boolean foundMatch = false; for (int i = 0; i < bElements.size(); i++) { if (compare(aElem, bElements.get(i))) { bElements.remove(i); foundMatch = true; break; } } if (!foundMatch) { return false; } } return bElements.isEmpty(); default: // 基本类型直接对比值 return a.equals(b); } } }
- 完全自定义逻辑,可根据需求扩展(比如忽略某些字段、模糊匹配数值范围等)
- 天然支持无序数组对比,同时覆盖Schema和值的校验
方案选择建议
- 优先选择JSONAssert/JsonUnit:开箱即用,代码量少,满足绝大多数通用场景
- 需要RFC 6902格式diff时,选择zjsonpatch+数组排序方案
- 有特殊自定义规则需求时,实现自定义递归对比逻辑
内容的提问来源于stack exchange,提问作者Sujoy
相关产品推荐
相关产品推荐

