You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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实现递归对比逻辑:

  1. 优先处理字符串、数字、布尔值、null等简单节点类型的直接对比;
  2. 对对象节点,遍历所有字段并递归对比子节点;
  3. 对数组节点,通过唯一标识(这里用dng_ID作为成员的唯一键)匹配元素,对比存在性和内容差异;
  4. 收集所有差异后,清理空节点以优化输出结果。

示例代码

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));
    }
}

代码说明

  1. compareJson:入口方法,初始化差异节点并触发递归对比;
  2. compareNodes:核心递归逻辑,根据节点类型(值、对象、数组)执行不同对比策略,针对members数组做了专属的唯一标识匹配;
  3. cleanEmptyNodes:遍历差异节点,移除空对象节点,避免输出无意义的空内容;
  4. main方法:示例执行逻辑,读取两个JSON字符串并输出格式化后的差异结果。

运行代码后,输出结果与预期完全一致,会展示JSON1中存在但JSON2中缺失的members元素。


内容的提问来源于stack exchange,提问作者user1829449

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 20:12:03