如何在Java中找出两个嵌套大型JSON的差异?
问题描述
我有两个包含嵌套元素的大型JSON,分别对应不同日期的LDAP数据,需要找出二者的差异。我研究过Jackson的JsonNode类,该类的equals方法可比较两个JsonNode,但无法返回具体差异。恳请各位提供最优实现方案,若有示例代码则更佳,谢谢。
JSON1
{ "id": "_6MrXgEx7Ee6GUol3yT5KIw", "name": "test", "fullName": "I_EXT_TEST", "members": [ { "mail": null, "roles": [ { "id": null, "name": "Author" }, { "id": null, "name": "Commenter" } ], "dng_ID": "aa@yy.com", "ad_ID": "CN=aaaaaaaaaa" }, { "mail": null, "roles": [ { "id": null, "name": "Author" } ], "dng_ID": "bb@yy.com", "ad_ID": "CN=bbbbbbbbbbb" }, { "mail": null, "roles": [ { "id": null, "name": "Commenter" } ], "dng_ID": "cc@yy.com", "ad_ID": "CN=ccccccc" }, { "mail": null, "roles": [ { "id": null, "name": "Commenter" } ], "dng_ID": "dd@yy.com", "ad_ID": "CN=ddddddd" } ] }
JSON2
{ "id": "_6MrXgEx7Ee6GUol3yT5KIw", "name": "test", "fullName": "I_EXT_TEST", "members": [ { "mail": null, "roles": [ { "id": null, "name": "Commenter" } ], "dng_ID": "cc@yy.com", "ad_ID": "CN=ccccccc" }, { "mail": null, "roles": [ { "id": null, "name": "Commenter" } ], "dng_ID": "dd@yy.com", "ad_ID": "CN=ddddddd" } ] }
预期输出
{ "members": [ { "mail": null, "roles": [ { "id": null, "name": "Author" }, { "id": null, "name": "Commenter" } ], "dng_ID": "aa@yy.com", "ad_ID": "CN=aaaaaaaaaa" }, { "mail": null, "roles": [ { "id": null, "name": "Author" } ], "dng_ID": "bb@yy.com", "ad_ID": "CN=bbbbbbbbbbb" } ] }
解决方案
方案思路
针对嵌套JSON的差异对比,尤其是包含数组的场景(比如这里的members数组),基于Jackson的JsonNode实现递归对比逻辑:
- 优先处理字符串、数字、布尔值、null等简单节点类型的直接对比;
- 对对象节点,遍历所有字段并递归对比子节点;
- 对数组节点,通过唯一标识(这里用
dng_ID作为成员的唯一键)匹配元素,对比存在性和内容差异; - 收集所有差异后,清理空节点以优化输出结果。
示例代码
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import java.util.*; public class JsonDiffer { private static final ObjectMapper mapper = new ObjectMapper(); // 对比两个JsonNode,返回JSON1相对于JSON2的差异内容 public static JsonNode compareJson(JsonNode json1, JsonNode json2) { ObjectNode diff = mapper.createObjectNode(); compareNodes(json1, json2, "", diff); cleanEmptyNodes(diff); return diff; } private static void compareNodes(JsonNode node1, JsonNode node2, String path, ObjectNode diff) { if (node1.equals(node2)) { return; } // 处理简单值节点 if (node1.isValueNode()) { diff.set(path.substring(1), node1); return; } // 处理对象节点 if (node1.isObject() && node2.isObject()) { Iterator<Map.Entry<String, JsonNode>> fields = node1.fields(); while (fields.hasNext()) { Map.Entry<String, JsonNode> entry = fields.next(); String key = entry.getKey(); JsonNode child1 = entry.getValue(); JsonNode child2 = node2.get(key); if (child2 == null) { diff.set(key, child1); } else { compareNodes(child1, child2, path + "." + key, diff); } } } // 处理数组节点 else if (node1.isArray() && node2.isArray()) { // 针对members数组,用dng_ID做唯一标识匹配 if ("members".equals(path.substring(1))) { Map<String, JsonNode> node2MemberMap = new HashMap<>(); for (JsonNode member : node2) { String dngId = member.get("dng_ID").asText(); node2MemberMap.put(dngId, member); } List<JsonNode> missingMembers = new ArrayList<>(); for (JsonNode member : node1) { String dngId = member.get("dng_ID").asText(); if (!node2MemberMap.containsKey(dngId)) { missingMembers.add(member); } else { // 递归对比成员内部字段 compareNodes(member, node2MemberMap.get(dngId), path + "." + dngId, diff); } } if (!missingMembers.isEmpty()) { diff.set("members", mapper.valueToTree(missingMembers)); } } else { // 通用数组对比(按索引逐个对比) for (int i = 0; i < node1.size(); i++) { JsonNode child1 = node1.get(i); JsonNode child2 = i < node2.size() ? node2.get(i) : null; compareNodes(child1, child2, path + "[" + i + "]", diff); } } } } // 清理空节点,避免冗余输出 private static void cleanEmptyNodes(ObjectNode node) { Iterator<Map.Entry<String, JsonNode>> fields = node.fields(); List<String> emptyFields = new ArrayList<>(); while (fields.hasNext()) { Map.Entry<String, JsonNode> entry = fields.next(); JsonNode child = entry.getValue(); if (child.isObject() && child.size() == 0) { emptyFields.add(entry.getKey()); } else if (child.isObject()) { cleanEmptyNodes((ObjectNode) child); if (child.size() == 0) { emptyFields.add(entry.getKey()); } } } emptyFields.forEach(node::remove); } public static void main(String[] args) throws Exception { // 读取示例JSON字符串 String json1Str = "{\n \"id\": \"_6MrXgEx7Ee6GUol3yT5KIw\",\n \"name\": \"test\",\n \"fullName\": \"I_EXT_TEST\",\n \"members\": [\n {\n \"mail\": null,\n \"roles\": [\n {\n \"id\": null,\n \"name\": \"Author\"\n },\n {\n \"id\": null,\n \"name\": \"Commenter\"\n }\n ],\n \"dng_ID\": \"aa@yy.com\",\n \"ad_ID\": \"CN=aaaaaaaaaa\"\n },\n {\n \"mail\": null,\n \"roles\": [\n {\n \"id\": null,\n \"name\": \"Author\"\n }\n ],\n \"dng_ID\": \"bb@yy.com\",\n \"ad_ID\": \"CN=bbbbbbbbbbb\"\n },\n {\n \"mail\": null,\n \"roles\": [\n {\n \"id\": null,\n \"name\": \"Commenter\"\n }\n ],\n \"dng_ID\": \"cc@yy.com\",\n \"ad_ID\": \"CN=ccccccc\"\n },\n {\n \"mail\": null,\n \"roles\": [\n {\n \"id\": null,\n \"name\": \"Commenter\"\n }\n ],\n \"dng_ID\": \"dd@yy.com\",\n \"ad_ID\": \"CN=ddddddd\"\n }\n ]\n}"; String json2Str = "{\n \"id\": \"_6MrXgEx7Ee6GUol3yT5KIw\",\n \"name\": \"test\",\n \"fullName\": \"I_EXT_TEST\",\n \"members\": [\n {\n \"mail\": null,\n \"roles\": [\n {\n \"id\": null,\n \"name\": \"Commenter\"\n }\n ],\n \"dng_ID\": \"cc@yy.com\",\n \"ad_ID\": \"CN=ccccccc\"\n },\n {\n \"mail\": null,\n \"roles\": [\n {\n \"id\": null,\n \"name\": \"Commenter\"\n }\n ],\n \"dng_ID\": \"dd@yy.com\",\n \"ad_ID\": \"CN=ddddddd\"\n }\n ]\n}"; JsonNode json1 = mapper.readTree(json1Str); JsonNode json2 = mapper.readTree(json2Str); JsonNode diffResult = compareJson(json1, json2); // 格式化输出差异结果 System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(diffResult)); } }
代码说明
- compareJson:入口方法,初始化差异节点并触发递归对比;
- compareNodes:核心递归逻辑,根据节点类型(值、对象、数组)执行不同对比策略,针对
members数组做了专属的唯一标识匹配; - cleanEmptyNodes:遍历差异节点,移除空对象节点,避免输出无意义的空内容;
- main方法:示例执行逻辑,读取两个JSON字符串并输出格式化后的差异结果。
运行代码后,输出结果与预期完全一致,会展示JSON1中存在但JSON2中缺失的members元素。
内容的提问来源于stack exchange,提问作者user1829449
相关产品推荐
相关产品推荐

