两个对象之间的通用深度差异 [英] Generic deep diff between two objects

查看:136
本文介绍了两个对象之间的通用深度差异的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有两个对象: oldObj newObj

oldObj 中的数据用于填充表单, newObj 是用户更改此表单中的数据并提交的结果。

The data in oldObj was used to populate a form and newObj is the result of the user changing data in this form and submitting it.

两个对象都很深,即。它们具有对象或对象数组等属性 - 它们可以是n级深度,因此diff算法需要递归。

Both objects are deep, ie. they have properties that are objects or arrays of objects etc - they can be n levels deep, thus the diff algorithm needs to be recursive.

现在我需要不仅仅是数字从 oldObj newObj 的更改内容(如添加/更新/删除),以及如何最好地表示它。

Now I need to not just figure out what was changed (as in added/updated/deleted) from oldObj to newObj, but also how to best represent it.

到目前为止,我的想法只是构建一个 genericDeepDiffBetweenObjects 方法,该方法将返回表单上的对象 {add:{...},upd:{...},del:{...}} 然后我想:其他人之前必须要这个。

So far my thoughts was to just build a genericDeepDiffBetweenObjects method that would return an object on the form {add:{...},upd:{...},del:{...}} but then I thought: somebody else must have needed this before.

所以...有没有人知道一个库或一段代码会做到这一点,也许有更好的方式来表示差异(以某种方式仍然是JSON可序列化的)?

So... does anyone know of a library or a piece of code that will do this and maybe have an even better way of representing the difference (in a way that is still JSON serializable)?

我想到了一种更好的方式来表示更新的数据,使用与 newObj 相同的对象结构,但将所有属性值转换为for上的对象m:

I have thought of a better way to represent the updated data, by using the same object structure as newObj, but turning all property values into objects on the form:

{type: '<update|create|delete>', data: <propertyValue>}

因此,如果 newObj.prop1 ='新值'并且 oldObj.prop1 ='旧值'它会设置 returnObj.prop1 = {type:'update',data:'new value'}

So if newObj.prop1 = 'new value' and oldObj.prop1 = 'old value' it would set returnObj.prop1 = {type: 'update', data: 'new value'}

当我们找到属于数组的属性时,它真的很毛茸茸,因为数组 [1,2,3] 应该算作等于 [2,3,1] ,这对于基于值的类型的数组来说很简单,比如string,int& bool,但是当涉及对象和数组等引用类型的数组时,实际上很难处理。

It gets truely hairy when we get to properties that are arrays, since the array [1,2,3] should be counted as equal to [2,3,1], which is simple enough for arrays of value based types like string, int & bool, but gets really difficult to handle when it comes to arrays of reference types like objects and arrays.

应该找到相等的示例数组:

Example arrays that should be found equal:

[1,[{c: 1},2,3],{a:'hey'}] and [{a:'hey'},1,[3,{c: 1},2]]

不仅非常复杂检查这种类型的深度值相等,还要找出一种表示可能变化的好方法。

Not only is it quite complex to check for this type of deep value equality, but also to figure out a good way to represent the changes that might be.

推荐答案

我写了一个做你想做的小课,你可以在这里测试它。

I wrote a little class that is doing what you want, you can test it here.

只有与您的建议不同的是我不考虑 [1,[{c:1},2,3],{a :'hey'}]和[{a:'hey'},1,[3,{c:1},2]] 相同,因为我认为数组不相等他们的元素顺序不一样。当然,如果需要,可以更改。此代码还可以进一步增强,将函数作为参数,用于根据传递的原始值以任意方式格式化diff对象(现在这个作业由compareValues方法完成)。

Only thing that is different from your proposal is that I don't consider [1,[{c: 1},2,3],{a:'hey'}] and [{a:'hey'},1,[3,{c: 1},2]] to be same, because I think that arrays are not equal if order of their elements is not same. Of course this can be changed if needed. Also this code can be further enhanced to take function as argument that will be used to format diff object in arbitrary way based on passed primitive values (now this job is done by "compareValues" method).

var deepDiffMapper = function() {
    return {
        VALUE_CREATED: 'created',
        VALUE_UPDATED: 'updated',
        VALUE_DELETED: 'deleted',
        VALUE_UNCHANGED: 'unchanged',
        map: function(obj1, obj2) {
            if (this.isFunction(obj1) || this.isFunction(obj2)) {
                throw 'Invalid argument. Function given, object expected.';
            }
            if (this.isValue(obj1) || this.isValue(obj2)) {
                return {
                    type: this.compareValues(obj1, obj2),
                    data: (obj1 === undefined) ? obj2 : obj1
                };
            }

            var diff = {};
            for (var key in obj1) {
                if (this.isFunction(obj1[key])) {
                    continue;
                }

                var value2 = undefined;
                if ('undefined' != typeof(obj2[key])) {
                    value2 = obj2[key];
                }

                diff[key] = this.map(obj1[key], value2);
            }
            for (var key in obj2) {
                if (this.isFunction(obj2[key]) || ('undefined' != typeof(diff[key]))) {
                    continue;
                }

                diff[key] = this.map(undefined, obj2[key]);
            }

            return diff;

        },
        compareValues: function(value1, value2) {
            if (value1 === value2) {
                return this.VALUE_UNCHANGED;
            }
            if (this.isDate(value1) && this.isDate(value2) && value1.getTime() === value2.getTime()) {
                return this.VALUE_UNCHANGED;
            }
            if ('undefined' == typeof(value1)) {
                return this.VALUE_CREATED;
            }
            if ('undefined' == typeof(value2)) {
                return this.VALUE_DELETED;
            }

            return this.VALUE_UPDATED;
        },
        isFunction: function(obj) {
            return {}.toString.apply(obj) === '[object Function]';
        },
        isArray: function(obj) {
            return {}.toString.apply(obj) === '[object Array]';
        },
        isDate: function(obj) {
            return {}.toString.apply(obj) === '[object Date]';
        },
        isObject: function(obj) {
            return {}.toString.apply(obj) === '[object Object]';
        },
        isValue: function(obj) {
            return !this.isObject(obj) && !this.isArray(obj);
        }
    }
}();


var result = deepDiffMapper.map({
      a:'i am unchanged',
      b:'i am deleted',
      e:{ a: 1,b:false, c: null},
      f: [1,{a: 'same',b:[{a:'same'},{d: 'delete'}]}],
      g: new Date('2017.11.25')
  },
  {
      a:'i am unchanged',
      c:'i am created',
      e:{ a: '1', b: '', d:'created'},
      f: [{a: 'same',b:[{a:'same'},{c: 'create'}]},1],
      g: new Date('2017.11.25')
  });
console.log(result);

这篇关于两个对象之间的通用深度差异的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆