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

新手上路,请多包涵

我有两个对象: oldObjnewObj

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

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

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

到目前为止,我的想法是构建一个通用的 genericDeepDiffBetweenObjects 方法,该方法将返回一个 {add:{...},upd:{...},del:{...}} 形式的对象,但后来我想:有人else 之前一定需要这个。

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

更新:

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

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

因此,如果 newObj.prop1 = 'new value'oldObj.prop1 = 'old value' 它将设置 returnObj.prop1 = {type: 'update', data: 'new value'}

更新 2:

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

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

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

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

原文由 Martin Jespersen 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.5k
2 个回答

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

唯一与你的提议不同的是我不考虑

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

原文由 sbgoran 发布,翻译遵循 CC BY-SA 4.0 许可协议

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

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

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

结果 o1 对应但在 o2 中具有不同值的部分:

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

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

原文由 drzaus 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题