两个对象之间的通用深度差异

2022-08-29 23:56:53

我有两个对象:和 。oldObjnewObj

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

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

现在,我不仅需要弄清楚从 到 更改了什么(如添加/更新/删除),还需要弄清楚如何最好地表示它。oldObjnewObj

到目前为止,我的想法只是构建一个可以在窗体上返回对象的方法,但后来我想:以前一定有人需要这个。genericDeepDiffBetweenObjects{add:{...},upd:{...},del:{...}}

所以。。。有没有人知道一个库或一段代码可以做到这一点,并且可能有更好的方法来表示差异(以一种仍然JSON可序列化的方式)?

更新:

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

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

所以如果和它会设置newObj.prop1 = 'new value'oldObj.prop1 = 'old value'returnObj.prop1 = {type: 'update', data: 'new value'}

更新 2:

当我们到达数组的属性时,它变得非常毛茸茸的,因为数组应该被计算为等于 ,这对于基于值的类型(如字符串,int和bool)的数组来说非常简单,但是当涉及到对象和数组等引用类型的数组时,处理起来非常困难。[1,2,3][2,3,1]

应找到相等的示例数组:

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

不仅要检查这种类型的深度价值相等性,而且要找出一种表达可能变化的好方法。


答案 1

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

与你的建议唯一不同的是,我不认为

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

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

是相同的,因为我认为数组如果元素的顺序不相同,则数组不相等。当然,如果需要,可以更改。此外,此代码可以进一步增强,以将函数作为参数,该参数将用于根据传递的基元值以任意方式格式化diff对象(现在这项工作是通过“compareValues”方法完成的)。

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 (obj2[key] !== undefined) {
          value2 = obj2[key];
        }

        diff[key] = this.map(obj1[key], value2);
      }
      for (var key in obj2) {
        if (this.isFunction(obj2[key]) || diff[key] !== undefined) {
          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 (value1 === undefined) {
        return this.VALUE_CREATED;
      }
      if (value2 === undefined) {
        return this.VALUE_DELETED;
      }
      return this.VALUE_UPDATED;
    },
    isFunction: function (x) {
      return Object.prototype.toString.call(x) === '[object Function]';
    },
    isArray: function (x) {
      return Object.prototype.toString.call(x) === '[object Array]';
    },
    isDate: function (x) {
      return Object.prototype.toString.call(x) === '[object Date]';
    },
    isObject: function (x) {
      return Object.prototype.toString.call(x) === '[object Object]';
    },
    isValue: function (x) {
      return !this.isObject(x) && !this.isArray(x);
    }
  }
}();


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);

答案 2

使用下划线,一个简单的差异:

var o1 = {a: 1, b: 2, c: 2},
    o2 = {a: 2, b: 1, c: 2};

_.omit(o1, function(v,k) { return o2[k] === v; })

结果对应于但具有不同值的部分:o1o2

{a: 1, b: 2}

对于深度差异来说,情况会有所不同:

function diff(a,b) {
    var r = {};
    _.each(a, function(v,k) {
        if(b[k] === v) return;
        // but what if it returns an empty object? still attach?
        r[k] = _.isObject(v)
                ? _.diff(v, b[k])
                : v
            ;
        });
    return r;
}

正如@Juhana在注释中指出的那样,上述只是一个差异a-->b,不可(这意味着b中的额外属性将被忽略)。请改用 a-->b-->a:

(function(_) {
  function deepDiff(a, b, r) {
    _.each(a, function(v, k) {
      // already checked this or equal...
      if (r.hasOwnProperty(k) || b[k] === v) return;
      // but what if it returns an empty object? still attach?
      r[k] = _.isObject(v) ? _.diff(v, b[k]) : v;
    });
  }

  /* the function */
  _.mixin({
    diff: function(a, b) {
      var r = {};
      deepDiff(a, b, r);
      deepDiff(b, a, r);
      return r;
    }
  });
})(_.noConflict());

请参阅 http://jsfiddle.net/drzaus/9g5qoxwj/ 了解完整示例+测试+mixins