获取两个 json 对象的差异

发布于 2024-12-20 03:07:00 字数 3088 浏览 2 评论 0原文

场景:我想要一个比较两个 JSON 对象的函数,并返回一个包含差异列表的 JSON 对象,如果可能的话还返回更多数据,例如覆盖率指标。

var madrid = '{"type":"team","description":"Good","trophies":[{"ucl":"10"}, {"copa":"5"}]}';
var barca = '{"type":"team","description":"Bad","trophies":[{"ucl":"3"}]}';

如果我运行 compare(madrid, barca) 返回的对象可能看起来像:

{"description" : "Bad", "trophies":[{"ucl":"3"}, {"copa":"5"}]}; 

或者类似的东西,你明白了。

有谁知道这个问题的解决方案?我已经找到一个 插件,但我想知道是否有其他选择。

Scenario: I want a function that compares two JSON-objects, and returns a JSON-object with a list of the differences and if possible more data such as coverage metrics.

var madrid = '{"type":"team","description":"Good","trophies":[{"ucl":"10"}, {"copa":"5"}]}';
var barca = '{"type":"team","description":"Bad","trophies":[{"ucl":"3"}]}';

If i ran compare(madrid, barca) the returned object could look something like:

{"description" : "Bad", "trophies":[{"ucl":"3"}, {"copa":"5"}]}; 

Or something similar, you get the idea.

Does anyone know of a solution to this? I've already found one plugin, but I'd like to know if there are any alternatives.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

一抹苦笑 2024-12-27 03:07:00

可以使用按对象键进行迭代的递归函数。然后使用 Object.is 来测试 NaNnull。然后测试第二个对象是否是转换为 false 的类型,例如 0NaNnull
列出两个对象的键并将它们连接起来以测试 obj1 中缺少的键,然后迭代它。

当相同键值之间存在差异时,它存储object2的值并继续。如果两个键值都是对象,则意味着可以递归比较,所以确实如此。

function diff(obj1, obj2) {
    const result = {};
    if (Object.is(obj1, obj2)) {
        return undefined;
    }
    if (!obj2 || typeof obj2 !== 'object') {
        return obj2;
    }
    Object.keys(obj1 || {}).concat(Object.keys(obj2 || {})).forEach(key => {
        if(obj2[key] !== obj1[key] && !Object.is(obj1[key], obj2[key])) {
            result[key] = obj2[key];
        }
        if(typeof obj2[key] === 'object' && typeof obj1[key] === 'object') {
            const value = diff(obj1[key], obj2[key]);
            if (value !== undefined) {
                result[key] = value;
            }
        }
    });
    return result;
}

上面的代码是 BSD 许可的,可以在任何地方使用。

测试链接: https://jsfiddle.net/gartz/vy9zaof2/54/

一个重要的观察是,这会将数组转换为对象并比较相同索引位置的值。由于需要额外的复杂性,还有许多其他方法可以比较此函数未涵盖的数组。

编辑 2019 年 2 月 15 日:此答案已更改以添加新的 ES2017 语法并修复来自


这只是一个开始,我还没有测试过它,但我从一个过滤器或比较器函数开始,它是递归的,可以根据需要更改它以获得优先结果。

function filter(obj1, obj2) {
    var result = {};
    for(key in obj1) {
        if(obj2[key] != obj1[key]) result[key] = obj2[key];
        if(typeof obj2[key] == 'array' && typeof obj1[key] == 'array') 
            result[key] = arguments.callee(obj1[key], obj2[key]);
        if(typeof obj2[key] == 'object' && typeof obj1[key] == 'object') 
            result[key] = arguments.callee(obj1[key], obj2[key]);
    }
    return result;
}

测试:http://jsfiddle.net/gartz/Q3BtG/2/

It's possible to use a recursive function that iterates by the object keys. Then use the Object.is to test for NaN and null. Then test if the second object is the type that cast to false like 0, NaN, or null.
List the keys of both objects and concatenate them to test of missing keys in the obj1 and then iterate it.

When there is a difference between the same key values, it stores the value of object2 and proceeds. If both key values are object means that can be recursively compared and so it does.

function diff(obj1, obj2) {
    const result = {};
    if (Object.is(obj1, obj2)) {
        return undefined;
    }
    if (!obj2 || typeof obj2 !== 'object') {
        return obj2;
    }
    Object.keys(obj1 || {}).concat(Object.keys(obj2 || {})).forEach(key => {
        if(obj2[key] !== obj1[key] && !Object.is(obj1[key], obj2[key])) {
            result[key] = obj2[key];
        }
        if(typeof obj2[key] === 'object' && typeof obj1[key] === 'object') {
            const value = diff(obj1[key], obj2[key]);
            if (value !== undefined) {
                result[key] = value;
            }
        }
    });
    return result;
}

The code above is BSD licensed and can be used anywhere.

Test link: https://jsfiddle.net/gartz/vy9zaof2/54/

An important observation, this will convert arrays to objects and compare the values in the same index position. There are many other ways to compare arrays not covered by this function due to the required extra complexity.

EDIT 2/15/2019: This answer was changed to add the new ES2017 syntax and fix use-cases from comments.


This is just a kickoff, I haven't tested it, but I began with a filter or comparator function, that is recursive, change it however you need to get priority results.

function filter(obj1, obj2) {
    var result = {};
    for(key in obj1) {
        if(obj2[key] != obj1[key]) result[key] = obj2[key];
        if(typeof obj2[key] == 'array' && typeof obj1[key] == 'array') 
            result[key] = arguments.callee(obj1[key], obj2[key]);
        if(typeof obj2[key] == 'object' && typeof obj1[key] == 'object') 
            result[key] = arguments.callee(obj1[key], obj2[key]);
    }
    return result;
}

Tests: http://jsfiddle.net/gartz/Q3BtG/2/

拍不死你 2024-12-27 03:07:00

贡献我对 Gabriel Gartz 版本的更改。这个在严格模式下工作并删除数组检查 - 将始终为 false。它还从差异中删除空节点。

//http://stackoverflow.com/questions/679915/how-do-i-test-for-an-empty-javascript-object
var isEmptyObject = function(obj) {
    var name;
    for (name in obj) {
        return false;
    }
    return true;
};

//http://stackoverflow.com/questions/8431651/getting-a-diff-of-two-json-objects
var diff = function(obj1, obj2) {
    var result = {};
    var change;
    for (var key in obj1) {
        if (typeof obj2[key] == 'object' && typeof obj1[key] == 'object') {
            change = diff(obj1[key], obj2[key]);
            if (isEmptyObject(change) === false) {
                result[key] = change;
            }
        }
        else if (obj2[key] != obj1[key]) {
            result[key] = obj2[key];
        }
    }
    return result;
};

contributing back my changes to Gabriel Gartz version. This one works in strict mode and removes the array check - will always be false. It also removes empty nodes from the diff.

//http://stackoverflow.com/questions/679915/how-do-i-test-for-an-empty-javascript-object
var isEmptyObject = function(obj) {
    var name;
    for (name in obj) {
        return false;
    }
    return true;
};

//http://stackoverflow.com/questions/8431651/getting-a-diff-of-two-json-objects
var diff = function(obj1, obj2) {
    var result = {};
    var change;
    for (var key in obj1) {
        if (typeof obj2[key] == 'object' && typeof obj1[key] == 'object') {
            change = diff(obj1[key], obj2[key]);
            if (isEmptyObject(change) === false) {
                result[key] = change;
            }
        }
        else if (obj2[key] != obj1[key]) {
            result[key] = obj2[key];
        }
    }
    return result;
};
心头的小情儿 2024-12-27 03:07:00

您可以使用 rus-diff https://github.com/mirek/node-rus-diff 创建 MongoDB 兼容(重命名/取消设置/设置)差异:

// npm install rus-diff
var madrid = {"type":"team","description":"Good","trophies":[{"ucl":"10"}, {"copa":"5"}]};
var barca = {"type":"team","description":"Bad","trophies":[{"ucl":"3"}]};
var rusDiff = require('rus-diff').rusDiff
console.log(rusDiff(madrid, barca))

输出:

{ '$unset': { 'trophies.1': true },
  '$set': { description: 'Bad', 'trophies.0.ucl': '3' } }

You can use rus-diff https://github.com/mirek/node-rus-diff which creates MongoDB compatible (rename/unset/set) diff:

// npm install rus-diff
var madrid = {"type":"team","description":"Good","trophies":[{"ucl":"10"}, {"copa":"5"}]};
var barca = {"type":"team","description":"Bad","trophies":[{"ucl":"3"}]};
var rusDiff = require('rus-diff').rusDiff
console.log(rusDiff(madrid, barca))

Outputs:

{ '$unset': { 'trophies.1': true },
  '$set': { description: 'Bad', 'trophies.0.ucl': '3' } }
維他命╮ 2024-12-27 03:07:00

对 Gabriel Gartz 版本的又一修改。此显示元组 [oldValue, newValue] 中每个键之前/之后的值,并且不包含差异中的空分支

function diffObj( obj1, obj2 ) {

    const result = {};
    if ( Object.is( obj1, obj2 ) ) return undefined;
    if ( ! obj2 || typeof obj2 !== 'object' ) return obj2;
    Object.keys( obj1 || {} ).concat( Object.keys( obj2 || {} ) ).forEach( key => {

        let val;
        if ( obj2[ key ] !== obj1[ key ] && ! Object.is( obj1[ key ], obj2[ key ] ) ) val = [ obj1[ key ], obj2[ key ] ];

        if ( typeof obj2[ key ] === 'object' && typeof obj1[ key ] === 'object' ) {

            const value = diffObj( obj1[ key ], obj2[ key ] );
            if ( value !== undefined ) val = value;

        } else if ( val !== undefined ) {

            result[ key ] = val;

        }

    } );
    return result;

}

One more modification to Gabriel Gartz's version. This one shows both values before/after for each key in a tuple [oldValue, newValue] and doesn't include empty branches in the diff

function diffObj( obj1, obj2 ) {

    const result = {};
    if ( Object.is( obj1, obj2 ) ) return undefined;
    if ( ! obj2 || typeof obj2 !== 'object' ) return obj2;
    Object.keys( obj1 || {} ).concat( Object.keys( obj2 || {} ) ).forEach( key => {

        let val;
        if ( obj2[ key ] !== obj1[ key ] && ! Object.is( obj1[ key ], obj2[ key ] ) ) val = [ obj1[ key ], obj2[ key ] ];

        if ( typeof obj2[ key ] === 'object' && typeof obj1[ key ] === 'object' ) {

            const value = diffObj( obj1[ key ], obj2[ key ] );
            if ( value !== undefined ) val = value;

        } else if ( val !== undefined ) {

            result[ key ] = val;

        }

    } );
    return result;

}
著墨染雨君画夕 2024-12-27 03:07:00

对我来说有效:

const a ={
    'kk':0,
    'k1':1,
    'k2':2,
    'k4':4, 
    'sub': {
        'xx':0,
        'x1':1,
        'x2':2,
        'x4':4, 
    } 
};
const b ={
    'kk':0,
    'k1':1,
    'k3':3, 
    'k4':44, 
    'sub': {
        'xx':0,
        'x1':1,
        'x3':3, 
        'x4':44, 
    }
};


function ObjectsDiff(o1,o2) {
    const typeObject = function(o){
        return typeof o === 'object';
    };
    const diff = function (o1, o2) {
        const result = {};
        // if first is item is not object
        if (!typeObject(o1) && typeObject(o2)) {
            return o2;
        }
        // if second is item is not object
        else if (typeObject(o1) && !typeObject(o2)) {
            return undefined;
        }
        // if they are equal
        else if (Object.is(o1, o2)) {
            return undefined;
        }
        const keys = Object.keys(o2);
        for (let i=0; i<keys.length; i++) {
            const key = keys[i];
            // if both are objects
            if ( typeObject(o1[key]) && typeObject(o2[key])) {
                // if equal, return nothing
                if ( Object.is(o1[key], o2[key]) ) {
                    // do nothing
                } else if (o1[key] === o2[key]) {
                    // do nothing
                } else {
                    result[key] = diff(o1[key],o2[key]);
                }
            } else if (o1[key] !== o2[key]) {
                result[key] = o2[key];
            } else {
                // do nothing
            }
        }
        return result;
    };
    return [
        diff(o1,o2),
        diff(o2,o1),
    ];
}

console.log( ObjectsDiff(a,b));

For me this worked:

const a ={
    'kk':0,
    'k1':1,
    'k2':2,
    'k4':4, 
    'sub': {
        'xx':0,
        'x1':1,
        'x2':2,
        'x4':4, 
    } 
};
const b ={
    'kk':0,
    'k1':1,
    'k3':3, 
    'k4':44, 
    'sub': {
        'xx':0,
        'x1':1,
        'x3':3, 
        'x4':44, 
    }
};


function ObjectsDiff(o1,o2) {
    const typeObject = function(o){
        return typeof o === 'object';
    };
    const diff = function (o1, o2) {
        const result = {};
        // if first is item is not object
        if (!typeObject(o1) && typeObject(o2)) {
            return o2;
        }
        // if second is item is not object
        else if (typeObject(o1) && !typeObject(o2)) {
            return undefined;
        }
        // if they are equal
        else if (Object.is(o1, o2)) {
            return undefined;
        }
        const keys = Object.keys(o2);
        for (let i=0; i<keys.length; i++) {
            const key = keys[i];
            // if both are objects
            if ( typeObject(o1[key]) && typeObject(o2[key])) {
                // if equal, return nothing
                if ( Object.is(o1[key], o2[key]) ) {
                    // do nothing
                } else if (o1[key] === o2[key]) {
                    // do nothing
                } else {
                    result[key] = diff(o1[key],o2[key]);
                }
            } else if (o1[key] !== o2[key]) {
                result[key] = o2[key];
            } else {
                // do nothing
            }
        }
        return result;
    };
    return [
        diff(o1,o2),
        diff(o2,o1),
    ];
}

console.log( ObjectsDiff(a,b));

独孤求败 2024-12-27 03:07:00

存在以下三种情况:

  1. 两个对象上都存在属性。

    • 如果两个对象中的属性相同,则从第二个对象中删除它(复制)。
  2. 第一个对象(原始)上存在属性,但第二个对象上不存在该属性。在本例中,将该属性添加到第二个对象。

  3. 最后,第二个对象上存在一个属性,但第一个对象上不存在,在这种情况下,不执行任何操作。

    函数比较(原始,复制){
      for (let [k, v] of Object.entries(original)) {
        if (typeof v === "object" && v !== null) {
          if (!copy.hasOwnProperty(k)) {
            复制[k] = v; // 2
          } 别的 {
            比较(v,复制?.[k]);
          }
        } 别的 {
          if (Object.is(v, 复制?.[k])) {
            删除副本?.[k]; // 1
          }
        }
      }
      返回 JSON.stringify(复制);
    }
    
    常量 
      马德里=
        '{"type":"团队","description":"好","奖杯":[{"ucl":"10"}, {"copa":"5"}]}',
      barca = '{"type":"team","description":"Bad","tropies":[{"ucl":"3"}]}',
      diff = 比较(JSON.parse(马德里), JSON.parse(巴萨));
    
    console.log(diff);

There are these three scenarios:

  1. A property exists on both the objects.

    • If the value of the property is same in both the objects, then delete it from the second object (copy).
  2. A property exists on the first object (original), but not on the second object. In this case add the property to the second object.

  3. And finally a property exists on the second object but not on the first, in this case, do nothing.

    function compare(original, copy) {
      for (let [k, v] of Object.entries(original)) {
        if (typeof v === "object" && v !== null) {
          if (!copy.hasOwnProperty(k)) {
            copy[k] = v; // 2
          } else {
            compare(v, copy?.[k]);
          }
        } else {
          if (Object.is(v, copy?.[k])) {
            delete copy?.[k]; // 1
          }
        }
      }
      return JSON.stringify(copy);
    }
    
    const 
      madrid =
        '{"type":"team","description":"Good","trophies":[{"ucl":"10"}, {"copa":"5"}]}',
      barca = '{"type":"team","description":"Bad","trophies":[{"ucl":"3"}]}',
      diff = compare(JSON.parse(madrid), JSON.parse(barca));
    
    console.log(diff);

故人的歌 2024-12-27 03:07:00

我遇到了同样的挑战,并创建了一个轻量级 API 来执行此操作,因为我发现其他 API 太复杂了 -获取对象之间的差异,以及将差异合并回 json 数据的能力。看起来它完全满足您的需要:

diff 对象使用 __deleted 或 __mergeAction 等标签来跟踪对象的更改,例如删除的数组记录或数组中的新/更新记录。否则,更改的非对象键(例如示例中的描述字段)只会显示在增量中。

// Get the delta between two json objects in a format that can be used by the mergeJson function
var edits = JsonDiffMerge.jsonDiff(originalData, updatedData);

// Merge the edits into the original object. The original object is not modified. The result is returned.
var merged = JsonDiffMerge.mergeJson(true, originalData, edits);

I had the same challenge and created a light API to do this as I found the others too complex -- get diff between objects, and ability to merge a diff back into json data. Looks like it does exactly what you need:

The diff object uses tags like __deleted or __mergeAction to keep track what the change is for an object such as a deleted array record, or a new / updated record in an array. Otherwise non-object keys that change (such as the description field in your example) just show up in the delta.

// Get the delta between two json objects in a format that can be used by the mergeJson function
var edits = JsonDiffMerge.jsonDiff(originalData, updatedData);

// Merge the edits into the original object. The original object is not modified. The result is returned.
var merged = JsonDiffMerge.mergeJson(true, originalData, edits);
长发绾君心 2024-12-27 03:07:00
function diff(obj1, obj2) {
    const result = {};
    if (Object.is(obj1, obj2)) {
        return undefined;
    }
    if (!obj2 || typeof obj2 !== 'object') {
        return obj2;
    }
    Object.keys(obj1 || {}).concat(Object.keys(obj2 || {})).forEach(key => {
        if(obj2[key] !== obj1[key] && !Object.is(obj1[key], obj2[key])) {
            result[key] = obj2[key];
        }
        if(typeof obj2[key] === 'object' && typeof obj1[key] === 'object') {
            const value = diff(obj1[key], obj2[key]);
            if (value !== undefined) {
                result[key] = value;
            }
        }
    });
    return result;
}

function diff(obj1, obj2) {
    const result = {};
    if (Object.is(obj1, obj2)) {
        return undefined;
    }
    if (!obj2 || typeof obj2 !== 'object') {
        return obj2;
    }
    Object.keys(obj1 || {}).concat(Object.keys(obj2 || {})).forEach(key => {
        if(obj2[key] !== obj1[key] && !Object.is(obj1[key], obj2[key])) {
            result[key] = obj2[key];
        }
        if(typeof obj2[key] === 'object' && typeof obj1[key] === 'object') {
            const value = diff(obj1[key], obj2[key]);
            if (value !== undefined) {
                result[key] = value;
            }
        }
    });
    return result;
}

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文