JSON 大文件应该如何比较差异呢?

332 阅读3分钟

在数据驱动的现代世界中,JSON(JavaScript Object Notation)已成为最流行的数据交换格式之一。无论是在Web开发、API集成还是配置管理中,JSON文件的比较都是一个常见且重要的任务。然而在大文件场景,尤其是数据量达到 GB 级别,大多数的在线工具处理都会难以处理。我写了一个 JSON 大文件比较的开源项目,给大家提供一个便捷的方式处理这种应用场景。欢迎大家探讨交流思路。

项目地址:github.com/toddWang23/…
NPM 地址:www.npmjs.com/package/jso…
在线 Demo 地址:暂缺

使用方式

npm install json-file-comparator

或者命令的方式

npx json-file-comparator --reference=refpath --compare=comparepath --output=outputpath --size=10240

import {compareJSON2File} from "json-file-comparator";
import path from 'path'
import { fileURLToPath } from 'node:url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// 被比较的文件
const comparePath = path.join(__dirname, './compare.json')
// 比较的基准文件
const refPath = path.join(__dirname, './reference.json')
// 输出的文件路径
const outPath = path.join(__dirname, './output.json')

compareJSON2File(refPath, comparePath, outPath)

在上面的例子中,对比 compare.jsonreference.json 两个文件,将对象的不同之处写到 output.json 中。结果为对象类型的 JSON 格式,key 为差异节点 JSON Path 路径,value 为差异节点的具体不同。差异的类型分为:

  • ADD: compare.json 中新增该节点
  • REMOVED: compare.json 中该节点被删除
  • MOVED:reference.json 中该节点被移动了
  • VALUE_CHANGE:两个文件中,对应节点值有更改
  • MOVED_CHANGE:两个文件中,对应节点值有更改,并且也移动了

示例输出:

{
    "$.added-node": {
        "type": "add",
        "content": "newly added content, based on type various, it can be string/array/object/number"
    }, 
    "$.removed-node": {
        "type": "removed",
        "content": "removed content, based on type various, it can be string/array/object/number"
    },
    "$.moved-node": {
        "type": "move",
        "prevIndex": 0,
        "changedIndex": 2
    }, 
    "$.value-change-node": {
        "type": "value_change",
        "prevValue": "previous content in reference file, based on type various, it can be string/array/object/number",
        "changedValue": "updated content in compare file, based on type various, it can be string/array/object/number"
    },
    "$.move-change-node": {
        "type": "move_change",
        "prevIndex": 0,
        "changedIndex": 2,
         "prevValue": "previous content in reference file, based on type various, it can be string/array/object/number",
        "changedValue": "updated content in compare file, based on type various, it can be string/array/object/number"
    }
}

思路

传统的大文件处理思路通常采用流式处理,即每次只读取一个文件块,处理完毕后再读取下一个文件块。这种方法在处理线性数据时非常有效,但JSON文件的结构化特性要求我们采取更为精细的处理策略。在对比两个大型JSON文件时,我们需要同时读取两个文件,并对比相应的文件块。然而,JSON文件的变更,可能将一个节点从文件的头部移到尾部,因为流式处理通常只关注当前处理的文件块,而不会回溯或前瞻,这种流式对比的方式会很难识别到改动。

为了解决这个问题,我提出了一种策略:在对比过程中,我们需要捕获同一层级的所有节点信息。这样,即使节点跨越多个文件块,我们也能够准确地识别和对比它们。但如果JSON文件的结构较为简单,只有一层,我们则需要尽量减少节点信息的保留,以优化内存使用。

在这种情况下,我们需要保留以下信息:

  1. 下层节点的类型:用于校验节点是否符合预期的数据结构。
  2. 下层节点的开始与结束位置:用于继续对比后续的节点。
  3. 节点名称:用于识别和对比节点。

每一层的对比完成后,我们可以直接将结果写入文件,这样可以确保内存使用尽可能少。如果遇到变更的节点,并且该节点还有子节点,我们需要递归地进行对比,直到所有的子节点都被处理完毕。

项目规划

除了大文件的对比,in-memory 对象的对比也在计划之中。为了更好的让大家体验到这个类库,在线的 demo 网站也在筹备之中。另外,项目的性能优化也在持续的做,欢迎大家多多关注。