关于后端:镜之Json-Compare-Diff

2次阅读

共计 13049 个字符,预计需要花费 33 分钟才能阅读完成。

前言

“镜”寓意是凡事都有两面性,Json 比照也不例外!

因公司业务性能当中有一个履历的性能, 它有多个版本的 JSON 数据须要比照出每个版本的不同差别节点并且将差别搁置在一个新的 JSON 当中原有构造不能变动, 差别节点应用数组对象的模式存储, 前端点击标红即可显示多个版本的节点差别数据如下图

示例

// JSON One
{ 
    "employee":
    {
        "id": "1212",
        "fullName":"John Miles",
        "age": 34,
        "contact":
        {
            "email": "john@xyz.com",
            "phone": "9999999999"
        }
    }
}

// Json Two
{
    "employee":
    {
        "id": "1212",
        "ae86": "12162",
        "age": 34,
        "fullName": "John Miles111",
        "contact":
        {
            "email": "john@xyz.com",
            "phone": "我是改了的",
            "668": "999999991199"
        }
    }
}

能够看到 employee.ae86 是新增的。contact.668 也是新增的 phone 字段是批改了的

比照后的 Json

// 获取差别的节点 应用数组对象示意
{
    "employee/fullName/": [{"old": "John Miles"}, {"new": "John Miles111"}],
    "employee/contact/phone/": [{"old": "9999999999"}, {"new": "我是改了的"}],
    "employee/contact/668": [{"new": "999999991199"}],
    "employee/ae86": [{"new": "12162"}]
}

// 将差别节点的数据笼罩下来

{
  "employee" : {
    "id" : "1212",
    "fullName" : [ {"old" : "John Miles"}, {"new" : "John Miles111"} ],
    "age" : 34,
    "contact" : {
      "email" : "john@xyz.com",
      "phone" : [ {"old" : "9999999999"}, {"new" : "我是改了的"} ],
      "668" : [ {"new" : "999999991199"} ]
    },
    "ae86" : [ {"new" : "12162"} ]
  }
}

实现

一、失去差别点 Map

package com.yby6;


import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.commons.lang3.ObjectUtils;

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

/**
 * @author Yang Shuai
 * Create By 2023/8/26
 */

public class JsonComparerUtils2 {static ObjectMapper mapper = new ObjectMapper();

    public static void main(String[] args) {

        String s1 = "{ \n" +
                "\"employee\":\n" +
                "{\n" +
                "\"id\": \"1212\",\n" +
                "\"fullName\":\"John Miles\",\n" +
                "\"age\": 34,\n" +
                "\"contact\":\n" +
                "{\n" +
                "\"email\": \"john@xyz.com\",\n" +
                "\"phone\": \"9999999999\"\n" +
                "}\n" +
                "}\n" +
                "}";
        String s2 = "{\n" +
                "\"employee\":\n" +
                "{\n" +
                "\"id\": \"1212\",\n" +
                "\"ae86\": \"12162\",\n" +
                "\"age\": 34,\n" +
                "\"fullName\": \"John Miles111\",\n" +
                "\"contact\":\n" +
                "{\n" +
                "\"email\": \"john@xyz.com\",\n" +
                "\"phone\": \" 我是改了的 \",\n" +
                "\"668\": \"999999991199\"\n" +
                "}\n" +
                "}\n" +
                "}";
        try {

            // 将 json 转 Json 节点树
            JsonNode node1 = mapper.readTree(s1);
            JsonNode node2 = mapper.readTree(s2);

            List<String> ignoreKey = new ArrayList<>();

            // 获取两个 JSON 之间的差别
            Map<String, Object> nodesDiff = getNodesDiff(node1, node2,
                    "", ignoreKey);
            System.out.println(mapper.writeValueAsString(nodesDiff));
        } catch (IOException e) {throw new RuntimeException(e);
        }

    }


    /**
     * 失去节点差别
     *
     * @param node1     node1
     * @param node2     node2
     * @param path      门路
     * @param ignoreKey 疏忽要害
     * @return {@link Map}<{@link String}, {@link Object}>
     */
    private static Map<String, Object> getNodesDiff(JsonNode node1, JsonNode node2, String path, List<String> ignoreKey) {Map<String, Object> diff = new LinkedHashMap<>();

        String[] split = path.split("/");
        String filed = split[split.length - 1];


        if (!node1.getNodeType().equals(node2.getNodeType())) {addToMap(path, node1, node2, diff, "update");
        } else {switch (node1.getNodeType()) {
                case OBJECT:

                    if (node1.isObject() && !node1.isEmpty()) {for (Iterator<String> it = node1.fieldNames(); it.hasNext();) {String fieldName = it.next();

                            JsonNode childNode1 = node1.get(fieldName);
                            JsonNode childNode2 = node2.get(fieldName);

                            // 疏忽指定字段不比照
                            if (ignoreKey.contains(fieldName)) {continue;}

                            if (childNode2 != null) {Map<String, Object> nestedDiff = getNodesDiff(childNode1, childNode2, path + fieldName + "/", ignoreKey);
                                if (!nestedDiff.isEmpty()) {diff.putAll(nestedDiff);
                                }
                            } else {
                                // 旧的存在新的则不存在示意删除
                                addToMap(path + fieldName, childNode1, childNode1, diff, "delete");
                            }
                        }

                        for (Iterator<String> it = node2.fieldNames(); it.hasNext();) {String fieldName = it.next();
                            if (ignoreKey.contains(fieldName)) {continue;}
                            // 如果旧的没有这个数据那么示意新增
                            if (node1.get(fieldName) == null) {addToMap(path + fieldName, null, node2.get(fieldName), diff, "add");
                            }
                        }
                    }


                    break;
                case ARRAY:
                    // 判断两个数组的长度不一样则须要将两个数组的长度补齐
                    if (node1.size() > 0 && node2.size() > 0 && node1.size() != node2.size()) {

                        try {String m1 = mapper.writeValueAsString(node1);
                            String m2 = mapper.writeValueAsString(node2);

                            List list1 = mapper.readValue(m1, List.class);
                            List list2 = mapper.readValue(m2, List.class);


                            if (list1.size() > list2.size()) {for (int i = list2.size(); i < list1.size(); i++) {String o = mapper.writeValueAsString(list1.get(i));
                                    JsonNode jsonNode = mapper.readTree(o);
                                    // 清空的
                                    clearNodeValues(jsonNode, ignoreKey);
                                    // 将 jsonNode2 增加到 jsonNode1 中
                                    ((ArrayNode) node2).add(jsonNode);
                                }
                            } else {for (int i = list1.size(); i < list2.size(); i++) {String o = mapper.writeValueAsString(list2.get(i));
                                    JsonNode jsonNode = mapper.readTree(o);
                                    // 清空的
                                    clearNodeValues(jsonNode, ignoreKey);
                                    ((ArrayNode) node1).add(jsonNode);
                                }
                            }


                            // 排序数组

                            List<JsonNode> firstList = mapper.readValue(node1.traverse(), new TypeReference<List<JsonNode>>() {});
                            List<JsonNode> secondList = mapper.readValue(node2.traverse(), new TypeReference<List<JsonNode>>() {});


                            // 补齐后递归比照
                            for (int i = 0; i < firstList.size(); i++) {Map<String, Object> nestedDiff = getNodesDiff(firstList.get(i), secondList.get(i), path + "[" + i + "]/", ignoreKey);
                                if (!nestedDiff.isEmpty()) {diff.putAll(nestedDiff);
                                }
                            }


                        } catch (IOException e) {throw new RuntimeException(e);
                        }

                    } else {
                        // 判断数组外面是不是对象
                        if (node1.size() > 0 && node1.get(0).getNodeType().equals(JsonNodeType.OBJECT)) {for (int i = 0; i < node1.size(); i++) {if (ignoreKey.contains(filed)) {break;}
                                Map<String, Object> nestedDiff = getNodesDiff(node1.get(i), node2.get(i), path + "[" + i + "]/", ignoreKey);
                                if (!nestedDiff.isEmpty()) {diff.putAll(nestedDiff);
                                }
                            }
                        } else {if (!node1.equals(node2)) {if (ignoreKey.contains(filed)) {break;}
                                addToMap(path, node1, node2, diff, "update");
                            }
                        }
                    }
                    break;
                case STRING:
                case BOOLEAN:
                case NUMBER:
                    if (ignoreKey.contains(filed)) {break;}

                    // 如果新的为空则为删除
                    if (ObjectUtils.isEmpty(node2)) {addToMap(path, node1, node2, diff, "delete");
                    }

                    if (!node1.equals(node2)) {addToMap(path, node1, node2, diff, "update");
                    }

                    break;
                default:
                    throw new IllegalArgumentException("Unsupported JSON type:" + node1.getNodeType().name());
            }
        }

        return diff;
    }


    /**
     * 清空节点参数
     *
     * @param node 节点
     */
    private static void clearNodeValues(JsonNode node, List<String> ignoreKey) {
        // 疏忽局部清空
        if (node.isObject()) {ObjectNode objectNode = (ObjectNode) node;
            objectNode.fields().forEachRemaining(entry -> {if (!ignoreKey.contains(entry.getKey())) {objectNode.replace(entry.getKey(), null);
                }
            });
        } else if (node.isArray()) {for (JsonNode childNode : node) {clearNodeValues(childNode, ignoreKey);
            }
        }
    }


    /**
     * 将两个 json 的差别增加到 Map
     *
     * @param path     门路
     * @param oldValue 旧值
     * @param newValue 新值
     * @param diff     diff
     */
    private static void addToMap(String path, JsonNode oldValue, JsonNode newValue, Map<String, Object> diff, String diffType) {List<Object> values = new ArrayList<>();
        HashMap<String, Object> map = new HashMap<>();
        map.put("old", oldValue != null ? getContent(oldValue) : "");
        map.put("new", newValue != null ? getContent(newValue) : "");
        map.put("diffType", diffType);
        values.add(map);
        diff.put(path, values.toArray(new Object[0]));
    }

    /**
     * 获取内容
     *
     * @param node 节点
     * @return {@link Object}
     */
    private static Object getContent(JsonNode node) {if (node.isBoolean()) {return node.asBoolean();
        } else if (node.isNumber()) {return node.asInt();
        } else if (node.isTextual()) {return node.asText();
        } else if (node.isObject()) {Map<String, Object> obj = new LinkedHashMap<>();
            for (Iterator<String> it = node.fieldNames(); it.hasNext();) {String propName = it.next();
                obj.put(propName, getContent(node.get(propName)));
            }
            return obj;
        } else if (node.isArray()) {ArrayNode array = (ArrayNode) node;
            Object[] contents = new Object[array.size()];
            for (int i = 0; i < array.size(); i++) {contents[i] = getContent(array.get(i));
            }
            return contents;
        } else if (node.isNull()) {return null;} else {throw new UnsupportedOperationException("不反对的 JSON 类型:" + node.getNodeType().name());
        }
    }

测试输入

{
    "employee/fullName/": [{
        "new": "John Miles111",
        "old": "John Miles",
        "diffType": "update"
    }],
    "employee/contact/phone/": [{
        "new": "我是改了的",
        "old": "9999999999",
        "diffType": "update"
    }],
    "employee/contact/668": [{
        "new": "999999991199",
        "old": "","diffType":"add"}],"employee/ae86": [{"new":"12162","old":"",
        "diffType": "add"
    }]
}

获取差别代码解说

这段代码是一个解决两个 JSON 节点之间差别的办法,以及一些辅助办法。上面我将解释每个办法的作用和代码逻辑:

getNodesDiff 办法

形容

该办法用于比拟两个 JSON 节点(node1node2)之间的差别,包含子节点差别,并返回一个示意差别的 Map

办法签名
private static Map<String, Object> getNodesDiff(JsonNode node1, JsonNode node2, String path, List<String> ignoreKey)
代码解释
  • diff 是一个用于存储差别的 LinkedHashMap
  • 首先,它依据门路 path 中的最初一个局部(field)来确定节点的类型。
  • 而后,它查看 node1node2 的节点类型是否雷同,如果不同,将差别增加到 diff 中。
  • 如果节点类型雷同,则依据节点类型进行解决,包含对象、数组、字符串、布尔值和数字类型。
  • 对于对象类型,它递归地比拟对象的字段,同时思考了一些非凡状况,例如疏忽指定的字段和 isValid 字段为 0 的状况。
  • 对于数组类型,它首先查看数组长度是否不统一,如果不统一,则尝试将两个数组的长度补齐,而后递归比拟数组元素。如果数组元素是对象类型,也会递归比拟对象。
  • 对于其余根本数据类型,它会间接比拟节点的值,如果不同,将差别增加到 diff 中。

clearNodeValues 办法

形容

这是一个辅助办法,用于清空节点的值,但保留节点构造。

办法签名
private static void clearNodeValues(JsonNode node, List<String> ignoreKey)
代码解释
  • 如果节点是对象类型,则清空对象中指定的字段,但疏忽 ignoreKey 中的字段。
  • 如果节点是数组类型,则递归地清空数组元素的值,但保留数组构造。

addToMap 办法

形容

这是一个辅助办法,用于将差别信息增加到差别 Map 中。

办法签名
private static void addToMap(String path, JsonNode oldValue, JsonNode newValue, Map<String, Object> diff, String diffType)
代码解释
  • 该办法将差别信息以指定的格局增加到 diff 中,包含门路 path、旧值 oldValue、新值 newValue 和差别类型 diffType

getContent 办法

形容

这是一个辅助办法,用于从 JsonNode 中提取内容。

办法签名
private static Object getContent(JsonNode node)
代码解释
  • 该办法依据 JsonNode 的类型提取内容,可能是布尔值、整数、字符串、对象、数组或 null 值。
  • 对于对象和数组类型,它递归提取内容并返回。

二、合并

    /**
     * 将差别利用到指定的 JSON 字符串,并返回解决后的字符串。*
     * @param json 要利用差别的原始 JSON 字符串
     * @param diff 差别内容,即 {@link #getNodesDiff} 返回的 Map 对象
     * @return 通过差别解决后的 JSON 字符串
     */
    public static String applyDiff(String json, Map<String, Object> diff) throws IOException {JsonNode node = mapper.readTree(json);
        for (Map.Entry<String, Object> entry : diff.entrySet()) {String[] path = entry.getKey().split("/");
            JsonNode parentNode = node;
            for (int i = 0; i < path.length - 1; i++) {
                // 如果是 null 则跳过
                if (parentNode == null) {continue;}
                // 如果该节点是数组那么解析一下
                if (parentNode.isArray()) {int index = getIndexFromPath(path[i]);
                    parentNode = parentNode.get(index);
                } else {parentNode = parentNode.get(path[i]);
                }
            }

            // 如果拿到的父节点是 null 则跳过
            if (parentNode == null) {continue;}

            String propertyName = path[path.length - 1];
            JsonNode childNode = parentNode.get(propertyName);


            if (entry.getValue() == null) {if (parentNode.isArray()) {((ArrayNode) parentNode).remove(Integer.parseInt(propertyName.substring(1, propertyName.length() - 1)));
                } else {((ObjectNode) parentNode).remove(propertyName);
                }
            } else {Object value = entry.getValue();
                // 是否是数组
                if (ArrayUtil.isArray(value)) {//                    ArrayNode arrayNode = mapper.createArrayNode(); // 新建一个空的数组节点
//                    arrayNode.addPOJO(value);
                    ObjectMapper objectMapper = new ObjectMapper();
                    JsonNode arrayNode = objectMapper.valueToTree(value);
//                    Object[] arr = (Object[]) value;
//                    for (Object item : arr) {//                        if (item != null) {
//                            // 将数组元素顺次退出新建的数组节点中,不须要解决逗号问题
//                            arrayNode.addPOJO(item);
//                        }
//                    }
                    if (childNode != null && !childNode.isMissingNode()) { // 曾经存在该属性,须要替换
                        ((ObjectNode) parentNode).replace(propertyName, arrayNode);
                    } else { // 不存在该属性,间接利用差别
                        // 如果父节点是数组,在数组开端增加新元素
                        // 如果父节点是对象,在该对象中增加新属性,值为空
                        if (parentNode.isArray()) {
                            int position = 0;
                            if (StringUtils.isNotBlank(propertyName)) {position = Integer.parseInt(propertyName.substring(1, propertyName.length() - 1));
                            }

                            while (position > parentNode.size()) {((ArrayNode) parentNode).add(mapper.createObjectNode().put("",""));
                            }
                            ((ArrayNode) parentNode).add(arrayNode);
                        } else {((ObjectNode) parentNode).set(propertyName, arrayNode);
                        }
                    }
                } else {String newValue = entry.getValue().toString();
                    if (childNode == null || childNode.isNull() || childNode.isMissingNode()) {if (parentNode.isArray()) { // 如果父节点是数组,在数组开端增加新元素
                            ((ArrayNode) parentNode).add(mapper.createObjectNode().put(propertyName, ""));
                        } else { // 如果父节点是对象,在该对象中增加新属性,值为空
                            ((ObjectNode) parentNode).put(propertyName, "");
                        }
                        childNode = parentNode.get(propertyName);
                    }
                    if (childNode.isValueNode()) {if (childNode.isBoolean()) {((ObjectNode) parentNode).put(propertyName, Boolean.parseBoolean(newValue));
                        } else if (childNode.isIntegralNumber()) {((ObjectNode) parentNode).put(propertyName, newValue);
                        } else if (childNode.isFloatingPointNumber()) {((ObjectNode) parentNode).put(propertyName, Double.parseDouble(newValue));
                        } else if (childNode.isTextual()) {
                            try {((ObjectNode) parentNode).put(propertyName, newValue.substring(1, newValue.length() - 1)); // 去掉 JSON 字符串外层的双引号
                            } catch (Exception e) {((ObjectNode) parentNode).put(propertyName, newValue);
                            }
                        }
                    } else {((ObjectNode) parentNode).set(propertyName, mapper.readTree(newValue));
                    }
                }
            }
        }
        return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(node);
    }


    /**
     * 移除方括号并将残余字符串解析为整数索引
     *
     * @param path 门路
     * @return int
     */
    private static int getIndexFromPath(String path) {return Integer.parseInt(path.substring(1, path.length() - 1));
    }

测试差别利用

{
    "employee": {
        "id": "1212",
        "ae86": [{
            "new": "12162",
            "old": "","diffType":"add"}],"age": 34,"fullName": [{"new":"John Miles111","old":"John Miles","diffType":"update"}],"contact": {"email":"john@xyz.com","phone": [{"new":" 我是改了的 ","old":"9999999999","diffType":"update"}],"668": [{"new":"999999991199","old":"",
                "diffType": "add"
            }]
        }
    }
}

差别利用代码解说

applyDiff 办法

形容

该办法将差别利用到指定的 JSON 字符串,并返回解决后的字符串。它承受一个原始的 JSON 字符串和一个差别的 Map,通常是从 getNodesDiff 办法获取的。

办法签名

public static String applyDiff(String json, Map<String, Object> diff) throws IOException

代码解释

  • 该办法首先应用 Jackson ObjectMapper mapper 将输出的 JSON 字符串 json 解析为一个 JsonNode 对象。
  • 遍历差别的 Map 中的每个条目,每个条目示意要利用到 JSON 的变更。
  • 对于每个条目,它通过 ‘/’ 来宰割条目标键(示意 JSON 内的门路),而后依照门路迭代 JSON 构造,更新以后节点指针。
  • 如果父节点为 null 或缺失,会跳过以后迭代。
  • 依据条目标值是否为 null,它要么移除一个节点,要么更新它:
- 如果值为 null,它会从 JSON 构造中移除节点。如果父节点是数组,则移除指定索引处的元素;否则,从对象中移除指定属性。
- 如果值不为 null,它会查看值是否为数组。如果是数组,它会创立一个新的 JSON 数组节点,并依据属性是否已存在,要么替换要么增加到父节点中。如果值不是数组,则依据其类型(布尔值、数字、字符串或 JSON 对象)更新 JSON 构造中的属性。
  • 最初,它应用 mapper 将批改后的 JsonNode 转换回 JSON 字符串,并返回后果的 JSON 字符串。

getIndexFromPath 办法

形容

这是一个公有的实用办法,用于移除字符串中的方括号,并将残余的字符串解析为整数索引。

办法签名

private static int getIndexFromPath(String path)

代码解释

  • 该办法以一个 path 字符串作为输出。
  • 它移除 path 字符串的首尾字符(假如它们是方括号),而后将残余的子串解析为整数索引。
  • 解析后的整数索引被返回。

最初

本期完结咱们下次再见👋~

,关注我不迷路,如果本篇文章对你有所帮忙,或者你有什么疑难,欢送在评论区留言,我个别看到都会回复的。大家点赞反对一下哟~ 💗

【选题思路】

基于两串不同的 JSON 数据进行比照进去差别再将差别利用到最新的 Json 字符串当中.

【写作提纲】

一、前言

因公司业务性能当中有一个履历的性能, 它有多个版本的 JSON 数据须要比照出每个版本的不同差别节点并且将差别搁置在一个新的 JSON 当中原有构造不能变动, 差别节点应用数组对象的模式存储, 前端点击标红即可显示多个版本的节点差别数据

二、示例

介绍两个 Json 的差别比照成果

三、实现

先失去两个 Json 的差别节点汇合、接着在最新的 Json 中转换 json 节点对象进行判断每个节点的字段是否合乎则插入到对应的字段当中!

本文由 mdnice 多平台公布

正文完
 0