基于diff算法实现多层JSON字符串对象的差异化比较

复制代码
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.util.*;

@Slf4j
public class JsonComparatorUtils {
    private static final ObjectMapper mapper = new ObjectMapper();

    public static void main(String[] args) throws IOException {


        String jsonBefore = "{"internal": false, "data": {"f8e37381-b7d6-4475-95d3-e763b64306e8": "33", "mw_instanceName": "kkkkkk", "flag": true}, "instanceName": "kkkkkk", "modelId": "104730838641410048", "groupId": "102706472617508864", "templateSearchParam": {"isExportTemp": false}, "type": "instance", "path": ["77170433597636608", "102706472617508864", "104730838641410048"], "itemName": "kkkkkk", "orgIds": [1]}";
        String jsonAfter = "{"internal": true, "data": {"f8e37381-b7d6-4475-95d3-e763b64306e8": "33", "mw_instanceName": "llllll", "flag": false}, "instanceName": "kkkkkk", "modelId": "104730838641410048", "groupId": "102706472617508864", "templateSearchParam": {"isExportTemp": false}, "type": "instance", "path": ["77170433597636608", "102706472617508864", "104730838641410048"], "itemName": "kkkkkk", "orgIds": [1]}";
        List<String> fieldsToMonitor = Arrays.asList("internal", "instanceName", "mw_instanceName");

        JsonNode jsonBeforeNode = mapper.readTree(jsonBefore);
        JsonNode jsonAfterNode = mapper.readTree(jsonAfter);

        DiffResult diffResult = generateHtmlWithHighlights(jsonBeforeNode, jsonAfterNode);

        System.out.println("HTML for json1Before: " + diffResult.getBefore());
        System.out.println("HTML for jsonAfter: " + diffResult.getAfter());
    }

    private static DiffResult generateHtmlWithHighlights(JsonNode jsonBeforeNode, JsonNode jsonAfterNode, List<String> fieldsToMonitor) {
        String jsonBefore = highlightDifference(jsonBeforeNode, jsonAfterNode, fieldsToMonitor);
        String jsonAfter = highlightDifference(jsonAfterNode, jsonBeforeNode, fieldsToMonitor);
        return new DiffResult(jsonBefore, jsonAfter);
    }

    private static String highlightDifference(JsonNode node1, JsonNode node2, List<String> fieldsToMonitor) {
        StringBuilder html = new StringBuilder();
        if (node1.isObject() && node2.isObject()) {
            ObjectNode objectNode1 = (ObjectNode) node1;
            ObjectNode objectNode2 = (ObjectNode) node2;
            for (String field : fieldsToMonitor) {
                JsonNode fieldNode1 = findField(objectNode1, field);
                JsonNode fieldNode2 = findField(objectNode2, field);
                if (fieldNode1 != null && fieldNode2 != null) {
                    if (!fieldNode1.equals(fieldNode2)) {
                        html.append("<span style='background-color: yellow;'>").append(field).append(": ").append(fieldNode1.asText()).append("</span><br>");
                    }
                } else if (fieldNode1 != null) {
                    html.append("<span style='background-color: yellow;'>").append(field).append(": ").append(fieldNode1.asText()).append("</span><br>");
                } else if (fieldNode2 != null) {
                    html.append("<span style='background-color: yellow;'>").append(field).append(": ").append(fieldNode2.asText()).append("</span><br>");
                }
            }
        }
        return html.toString();
    }

    private static JsonNode findField(ObjectNode node, String fieldName) {
        if (node.has(fieldName)) {
            return node.get(fieldName);
        }
        for (Iterator<String> it = node.fieldNames(); it.hasNext(); ) {
            String key = it.next();
            JsonNode childNode = node.get(key);
            if (childNode.isObject()) {
                JsonNode result = findField((ObjectNode) childNode, fieldName);
                if (result != null) {
                    return result;
                }
            }
        }
        return null;
    }


    public static DiffResult generateHtmlWithHighlights(JsonNode node1, JsonNode node2) {
        try {
            // 获取差异并进行高亮处理
            Map<String, String> differences = getDifferences(node1, node2);
            ObjectMapper newMapper = new ObjectMapper();
            ObjectNode highlightedNode = newMapper.createObjectNode();
            highlightDifference(node1, differences, highlightedNode, "");
            // 将差异节点转换为字符串
            String beforeStr = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(highlightedNode);

            highlightedNode = newMapper.createObjectNode();
            highlightDifference(node2, differences, highlightedNode, "");
            // 将差异节点转换为字符串
            String afterStr = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(highlightedNode);
            // 创建 DiffResult 对象并返回
            return new DiffResult(beforeStr, afterStr);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取两个 JSON 对象的差异
     * @param node1 before 节点
     * @param node2 after 节点
     * @return
     */
    private static Map<String, String> getDifferences(JsonNode node1, JsonNode node2) {
        Map<String, String> differences = new LinkedHashMap<>();
        compareJsonNodes(node1, node2, differences);
        return differences;
    }

    /**
     * 递归比较两个 JSON 节点的差异
     * @param node1 before 节点
     * @param node2 after 节点
     * @param differences  差异字段
     */
    private static void compareJsonNodes(JsonNode node1, JsonNode node2, Map<String, String> differences) {
        Iterator<String> fieldNames = node1.fieldNames();
        while (fieldNames.hasNext()) {
            String fieldName = fieldNames.next();
            JsonNode fieldValue1 = node1.get(fieldName);
            JsonNode fieldValue2 = node2.get(fieldName);

            // 如果是对象类型,则继续比较
            if (fieldValue1.isObject() && fieldValue2.isObject()) {
                compareJsonNodes(fieldValue1, fieldValue2, differences);
                // 跳过本次后续操作
                continue;
            }

            if (!Objects.equals(fieldValue1, fieldValue2)) {
                differences.put(fieldName, fieldValue1.toString());
            }

        }
    }

    private static void highlightDifference(JsonNode node, Map<String, String> differences,
                                            ObjectNode highlightedNode, String key) {
        try {
            Iterator<Map.Entry<String, JsonNode>> fields = node.fields();
            ObjectNode objectNode = null;
            while (fields.hasNext()) {
                Map.Entry<String, JsonNode> field = fields.next();
                String fieldName = field.getKey();
                JsonNode fieldValue = field.getValue();

                if (fieldValue.isObject()) {
                    // 如果是对象,递归查看内部属性;TODO 其实可以将内部value转map,查看是否存在differences中的字段。是则递归
                    log.info("key: {}, fieldName: {}", key, fieldName);
                    highlightDifference(fieldValue, differences, highlightedNode, fieldName);
                }
                if (StrUtil.isNotEmpty(key)) {
                    if (ObjUtil.isEmpty(objectNode)) {
                        // 初始化 objectNode
                        objectNode = new ObjectMapper().createObjectNode();
                    }
                    // 差异化包含 fieldName 设置html元素
                    if (differences.containsKey(fieldName)) {
                        objectNode.put(fieldName,
                                "<span style='background-color: yellow;'>" + fieldValue + "</font>");
                    } else {
                        // 差异化不包含正常添加数据
                        objectNode.set(fieldName, fieldValue);
                    }
                } else {
                    if (differences.containsKey(fieldName)) {
                        highlightedNode.put(fieldName,
                                "<span style='background-color: yellow;'>" + fieldValue + "</font>");
                    } else {
                        // 避免原生对象数据后期替换掉之前设置的 objectNode
                        if (highlightedNode.get(fieldName) == null) {
                            highlightedNode.set(fieldName, fieldValue);
                        }
                    }
                }
            }
            if (ObjUtil.isNotEmpty(objectNode) && StrUtil.isNotEmpty(key)) {
                highlightedNode.set(key, objectNode);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Data
    public static class DiffResult {
        private String before;
        private String after;

        public DiffResult(String before, String after) {
            this.before = before;
            this.after = after;
        }

    }
}
复制代码

 

posted @   XSWClevo  阅读(121)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示