zjsonpatch 比较json字符串源码分析

简介: zjsonpatch是一个对json字符串进行操作的java类库。通过代码分析,我们可以看到,使用zjsonpatch比较json是以source字符串为基准, 与最大公共子串进行比较,source多则remove srcNode,source少则add targetNode。

00.结论

通过代码分析,我们可以看到,使用zjsonpatch比较json是以source字符串为基准,
与最大公共子串进行比较,source多则remove srcNode
与最大公共子串进行比较,source少则add targetNode

以下为简单图例:
️lcs 为target和source两个json字符串的最大公共子串。
image

01.背景

zjsonpatch是一个对json字符串进行操作的java类库。
zjsonpatch github 地址:zjsonpatch
zjsonpatch json字符串比较注释版本version-1.4.6-with-comment
我们使用zjsonpatch 比较下面两个json字符串
target:

{
    "list": [{
        "id": 1566191147593281395
    }, {
        "id": 1566196494578281356
    }, {
        "id": 1566197027522281110
    }]
}

source:

{
    "list": [{
        "id": 1566196494578281356
    }, {
        "id": 1566197027522281110
    }]
}

示例代码

EnumSet<DiffFlags> flags = DiffFlags.dontNormalizeOpIntoMoveAndCopy().clone();
JsonNode diffResultNode = JsonDiff.asJson(actualNode, expectNode, flags);

Iterator<JsonNode> diffJsonNodeIterator = diffResultNode.iterator();

 while (diffJsonNodeIterator.hasNext()) {
            JsonNode node = diffJsonNodeIterator.next();
            String op = node.path("op").asText();
            String from = node.path("from").asText();
            String path = node.path("path").asText();
            JsonNode value = node.get("value");
            System.out.println("op:"+op+","+"from:"+from+",path:"+path+",value:"+value);
            
        }       

打印结果

op:add,from:,path:/list/0,value:{"id":1566191147593281395}

接下来我们就来分析一下zjsonpatch 如何比较json字符串的。

02.过程分析

zjsonpatch版本

<dependency>
            <groupId>com.flipkart.zjsonpatch</groupId>
            <artifactId>zjsonpatch</artifactId>
            <version>0.4.6</version>
        </dependency>

我在代码里写了比较详细的注释,有兴趣的朋友可以下载 zjsonpatch json字符串比较注释版本version-1.4.6-with-comment 进行测试。

step 1 比较操作主要通过JsonDiff类的 asJson 方法操作。

public static JsonNode asJson(final JsonNode source, final JsonNode target, EnumSet<DiffFlags> flags) {
        final List<Diff> diffs = new ArrayList<Diff>();
        List<Object> path = new ArrayList<Object>(0);

        // generating diffs in the order of their occurrence
        //按照资源的顺序构建不同的内容
        generateDiffs(diffs, path, source, target);

        if (!flags.contains(DiffFlags.OMIT_MOVE_OPERATION)) {

            // Merging remove & add to move operation

            compactDiffs(diffs);
        }

        if (!flags.contains(DiffFlags.OMIT_COPY_OPERATION)) {

            // Introduce copy operation

            introduceCopyOperation(source, target, diffs);
        }

        return getJsonNodes(diffs, flags);
    }

step 2 根据jsonNode的类型是NodeType.ARRAY还是NodeType.OBJECT做不同处理

private static void generateDiffs(List<Diff> diffs, List<Object> path, JsonNode source, JsonNode target) {
        if (!source.equals(target)) {
            final NodeType sourceType = NodeType.getNodeType(source);
            final NodeType targetType = NodeType.getNodeType(target);

            if (sourceType == NodeType.ARRAY && targetType == NodeType.ARRAY) {
                //both are arrays
                compareArray(diffs, path, source, target);
            } else if (sourceType == NodeType.OBJECT && targetType == NodeType.OBJECT) {
                //both are json
                compareObjects(diffs, path, source, target);
            } else {
                //can be replaced

                diffs.add(Diff.generateDiff(Operation.REPLACE, path, source, target));
            }
        }
    }

step 3 如果是NodeType.OBJECT 类型 比较source 和 target的 key,然后递归处理, 比较简单

private static void compareObjects(List<Diff> diffs, List<Object> path, JsonNode source, JsonNode target) {
        Iterator<String> keysFromSrc = source.fieldNames();
        while (keysFromSrc.hasNext()) {
            String key = keysFromSrc.next();
            if (!target.has(key)) {
                //remove case
                List<Object> currPath = getPath(path, key);
                diffs.add(Diff.generateDiff(Operation.REMOVE, currPath, source.get(key)));
                continue;
            }
            List<Object> currPath = getPath(path, key);
            //根据jsonNode的类型 ARRAY OBJECT 做不同处理 递归调用
            generateDiffs(diffs, currPath, source.get(key), target.get(key));
        }
        Iterator<String> keysFromTarget = target.fieldNames();
        while (keysFromTarget.hasNext()) {
            String key = keysFromTarget.next();
            if (!source.has(key)) {
                //add case
                List<Object> currPath = getPath(path, key);
                diffs.add(Diff.generateDiff(Operation.ADD, currPath, target.get(key)));
            }
        }
    }

step 4 如果是NodeType.ARRAY 类型 如何比较 是我们这次分析的重点

private static void compareArray(List<Diff> diffs, List<Object> path, JsonNode source, JsonNode target) {
        List<JsonNode> lcs = getLCS(source, target);
        //source 指针
        int srcIdx = 0;
        //target 指针
        int targetIdx = 0;
        //lcs 指针
        int lcsIdx = 0;
        //source 尺寸
        int srcSize = source.size();
        //target 尺寸
        int targetSize = target.size();
        //lcs 尺寸
        int lcsSize = lcs.size();
        //数组里的对象坐标
        int pos = 0;
        while (lcsIdx < lcsSize) {
            JsonNode lcsNode = lcs.get(lcsIdx);
            JsonNode srcNode = source.get(srcIdx);
            JsonNode targetNode = target.get(targetIdx);

            // Both are same as lcs node, nothing to do here
            // lcs node 和 src target都相同 则所有指针移动 继续比较
            if (lcsNode.equals(srcNode) && lcsNode.equals(targetNode)) {
                srcIdx++;
                targetIdx++;
                lcsIdx++;
                pos++;
            } else {
                // src node is same as lcs, but not targetNode
                //lcs拿到的值和src node值相同 但是和target node值不同
                // 说明target比source多,所以source 需要add 当前targetNode
                if (lcsNode.equals(srcNode)) {
                    //addition
                    List<Object> currPath = getPath(path, pos);
                    //targetNode 需要add 这个path的 node
                    diffs.add(Diff.generateDiff(Operation.ADD, currPath, targetNode));
                    //数组里的对象坐标指针移动
                    pos++;
                    //target指针移动 比较target 下一个node
                    targetIdx++;

                //targetNode node is same as lcs, but not src
                //lcs拿到的值和target node值相同 但是和src node值不同
                //删除操作 数组里的对象坐标指针不移动
                //说明source 比 target多 所以需要source remove 当前srcNode
                } else if (lcsNode.equals(targetNode)) {
                    //removal,
                    List<Object> currPath = getPath(path, pos);
                    //srcNode 需要remove 这个path的 node
                    diffs.add(Diff.generateDiff(Operation.REMOVE, currPath, srcNode));
                    //source指针移动 比较source 下一个node
                    srcIdx++;
                } else {
                    List<Object> currPath = getPath(path, pos);
                    //both are unequal to lcs node
                    //如果srcNode 和 targetNode 和 lcsNode都不同,则继续递归比较
                    generateDiffs(diffs, currPath, srcNode, targetNode);
                    //指针移动 比较source,target 下一个node
                    srcIdx++;
                    targetIdx++;
                    //数组里的对象坐标指针移动
                    pos++;
                }
            }
        }

        //最大公共子串和source,target比较完成后,
        // 检查source 和 target node节点是否都遍历比较完成
        // 如果没有完成 则继续递归调用
        while ((srcIdx < srcSize) && (targetIdx < targetSize)) {
            JsonNode srcNode = source.get(srcIdx);
            JsonNode targetNode = target.get(targetIdx);
            List<Object> currPath = getPath(path, pos);
            generateDiffs(diffs, currPath, srcNode, targetNode);
            srcIdx++;
            targetIdx++;
            pos++;
        }

        //处理剩余的target node
        pos = addRemaining(diffs, path, target, pos, targetIdx, targetSize);
        //处理剩余的source node
        removeRemaining(diffs, path, pos, srcIdx, srcSize, source);
    }

首先获取最大公共字串 getLCS(source, target)的集合,最大公共字串比较结果受到字符串内容顺序影响。

private static List<JsonNode> getLCS(final JsonNode first, final JsonNode second) {
        return ListUtils.longestCommonSubsequence(InternalUtils.toList((ArrayNode) first), InternalUtils.toList((ArrayNode) second));
    }

处理剩余的target node

private static Integer addRemaining(List<Diff> diffs, List<Object> path, JsonNode target, int pos, int targetIdx, int targetSize) {
        //比较完成之后,剩余的targetNode 需要 add 这个path的 node
        while (targetIdx < targetSize) {
            JsonNode jsonNode = target.get(targetIdx);
            List<Object> currPath = getPath(path, pos);
            diffs.add(Diff.generateDiff(Operation.ADD, currPath, jsonNode.deepCopy()));
            pos++;
            targetIdx++;
        }
        return pos;
    }

处理剩余的source node

private static Integer removeRemaining(List<Diff> diffs, List<Object> path, int pos, int srcIdx, int srcSize, JsonNode source) {
        //比较完成之后  剩余的srcNode 需要remove 这个path的 node
        while (srcIdx < srcSize) {
            List<Object> currPath = getPath(path, pos);
            diffs.add(Diff.generateDiff(Operation.REMOVE, currPath, source.get(srcIdx)));
            srcIdx++;
        }
        return pos;
    }
目录
相关文章
|
18天前
|
存储 JSON JavaScript
Python字典和JSON字符串相互转化方法
【2月更文挑战第18天】
68 3
|
18天前
|
JSON JavaScript 数据格式
JS 将 json 对象转成字符串并保留格式 - JSON.stringify()
JS 将 json 对象转成字符串并保留格式 - JSON.stringify()
70 0
|
7天前
|
SQL 存储 JSON
Hive 解析 JSON 字符串数据的实现方式
Hive 提供 `get_json_object` 函数解析 JSON 字符串,如 `{&quot;database&quot;:&quot;maxwell&quot;}`。`path` 参数使用 `$`、`.`、`[]` 和 `*` 来提取数据。示例中展示了如何解析复杂 JSON 并存储到表中。此外,Hive 3.0.0及以上版本内置 `JsonSerDe` 支持直接处理 JSON 文件,无需手动解析。创建表时指定 `JsonSerDe` 序列化器,并在 HDFS 上存放 JSON 文件,可以直接查询字段内容,方便快捷。
|
10天前
|
SQL JSON 监控
实时计算 Flink版产品使用合集之直接将 JSON 字符串解析为数组的内置函数如何解决
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
16天前
|
JSON JavaScript 前端开发
js将json字符串还原为json对象
【5月更文挑战第14天】js将json字符串还原为json对象
45 1
|
18天前
|
存储 JSON DataWorks
DataWorks产品使用合集之DataWorks将 MongoDB 中的数组类型写入到 DataWorks 的单个字段时,表示为字符串格式而非 JSON 格式如何解决
DataWorks作为一站式的数据开发与治理平台,提供了从数据采集、清洗、开发、调度、服务化、质量监控到安全管理的全套解决方案,帮助企业构建高效、规范、安全的大数据处理体系。以下是对DataWorks产品使用合集的概述,涵盖数据处理的各个环节。
29 3
|
18天前
|
JSON 分布式计算 大数据
MaxCompute产品使用合集之大数据计算MaxCompute 要提取JSON字符串中的所有key-value对,我该怎么操作
MaxCompute作为一款全面的大数据处理平台,广泛应用于各类大数据分析、数据挖掘、BI及机器学习场景。掌握其核心功能、熟练操作流程、遵循最佳实践,可以帮助用户高效、安全地管理和利用海量数据。以下是一个关于MaxCompute产品使用的合集,涵盖了其核心功能、应用场景、操作流程以及最佳实践等内容。
|
18天前
|
JSON 运维 Kubernetes
云效产品使用报错问题之流水线中配置了AppStack,构建时下载的制品内容为json字符串,如何解决
本合集将整理呈现用户在使用过程中遇到的报错及其对应的解决办法,包括但不限于账户权限设置错误、项目配置不正确、代码提交冲突、构建任务执行失败、测试环境异常、需求流转阻塞等问题。阿里云云效是一站式企业级研发协同和DevOps平台,为企业提供从需求规划、开发、测试、发布到运维、运营的全流程端到端服务和工具支撑,致力于提升企业的研发效能和创新能力。
|
18天前
|
JSON 数据格式 Python
py如何把字符串转出json
py如何把字符串转出json
10 0
|
18天前
|
JSON 数据格式
Json字符串与QVariantList 对象相互转换
Json字符串与QVariantList 对象相互转换
23 0