如何获得两个 JavaScript 对象图之间差异的列表?

IT技术 javascript json data-structures diff
2021-03-15 03:10:53

我希望能够获得两个 JavaScript 对象图之间所有差异的列表,以及发生增量的属性名称和值。

就其value而言,这些对象通常以 JSON 的形式从服务器中检索,并且通常不超过少数几层深(即它可能是一个本身具有数据的对象数组,然后是具有其他数据对象的数组)。

我不仅要查看基本属性的更改,还要查看数组成员数量的差异等。

如果我没有得到答案,我可能最终会自己写这篇文章,但希望有人已经完成了这项工作,或者知道有人做过这项工作。


编辑:这些对象通常在结构上彼此非常接近,所以我们不是在谈论彼此完全不同的对象,但可能有 3 或 4 个增量。

6个回答

这是对我的问题的部分、幼稚的解决方案 - 我将在进一步开发它时更新它。

function findDifferences(objectA, objectB) {
   var propertyChanges = [];
   var objectGraphPath = ["this"];
   (function(a, b) {
      if(a.constructor == Array) {
         // BIG assumptions here: That both arrays are same length, that
         // the members of those arrays are _essentially_ the same, and 
         // that those array members are in the same order...
         for(var i = 0; i < a.length; i++) {
            objectGraphPath.push("[" + i.toString() + "]");
            arguments.callee(a[i], b[i]);
            objectGraphPath.pop();
         }
      } else if(a.constructor == Object || (a.constructor != Number && 
                a.constructor != String && a.constructor != Date && 
                a.constructor != RegExp && a.constructor != Function &&
                a.constructor != Boolean)) {
         // we can safely assume that the objects have the 
         // same property lists, else why compare them?
         for(var property in a) {
            objectGraphPath.push(("." + property));
            if(a[property].constructor != Function) {
               arguments.callee(a[property], b[property]);
            }
            objectGraphPath.pop();
         }
      } else if(a.constructor != Function) { // filter out functions
         if(a != b) {
            propertyChanges.push({ "Property": objectGraphPath.join(""), "ObjectA": a, "ObjectB": b });
         }
      }
   })(objectA, objectB);
   return propertyChanges;
}

这是一个如何使用它以及它将提供的数据的示例(请原谅这个长示例,但我想使用一些相对重要的东西):

var person1 = { 
   FirstName : "John", 
   LastName : "Doh", 
   Age : 30, 
   EMailAddresses : [
      "john.doe@gmail.com", 
      "jd@initials.com"
   ], 
   Children : [ 
      { 
         FirstName : "Sara", 
         LastName : "Doe", 
         Age : 2 
      }, { 
         FirstName : "Beth", 
         LastName : "Doe", 
         Age : 5 
      } 
   ] 
};

var person2 = { 
   FirstName : "John", 
   LastName : "Doe", 
   Age : 33, 
   EMailAddresses : [
      "john.doe@gmail.com", 
      "jdoe@hotmail.com"
   ], 
   Children : [ 
      { 
         FirstName : "Sara", 
         LastName : "Doe", 
         Age : 3 
      }, { 
         FirstName : "Bethany", 
         LastName : "Doe", 
         Age : 5 
      } 
   ] 
};

var differences = findDifferences(person1, person2);

此时,differences如果数组序列化为 JSON ,则数组将如下所示:

[
   {
      "Property":"this.LastName", 
      "ObjectA":"Doh", 
      "ObjectB":"Doe"
   }, {
      "Property":"this.Age", 
      "ObjectA":30, 
      "ObjectB":33
   }, {
      "Property":"this.EMailAddresses[1]", 
      "ObjectA":"jd@initials.com", 
      "ObjectB":"jdoe@hotmail.com"
   }, {
      "Property":"this.Children[0].Age", 
      "ObjectA":2, 
      "ObjectB":3
   }, {
      "Property":"this.Children[1].FirstName", 
      "ObjectA":"Beth", 
      "ObjectB":"Bethany"
   }
]

thisProperty值是指被比较的对象的根。因此,该解决方案还没有正是我所需要的,但它是相当的接近。

希望这对那里的人有用,如果您有任何改进建议,我会全力以赴;我昨晚很晚(即今天早上)写了这篇文章,可能有些事情我完全忽略了。

谢谢。

非常感谢,尽管我想建立一个差异对象
2021-04-21 03:10:53
伙计们,请注意:在 ES5 arguments.callee 在严格模式下是被禁止的。 developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
2021-05-12 03:10:53

查看现有答案后,我注意到https://github.com/flitbit/diff库尚未列为解决方案。

从我的研究来看,这个库在积极开发、贡献和解决不同对象的挑战方面似乎是最好的。这对于在服务器端创建差异并仅将更改的位传递给客户端非常方便。

是的,JS 的世界发生了翻天覆地的变化。我从没想过我会用 JavaScript 编写后端程序!谢谢,@JasonBunting
2021-04-17 03:10:53
我将我的官方答案从我自己的答案更改为您的答案,因为这是一个如此古老的问题,自从我 7 年前编写它以来,JavaScript 的世界发生了重大变化!:)
2021-05-15 03:10:53

有一个objectDiff 库可以让你做到这一点。在其演示页面上,您可以看到两个 javascript 对象之间的区别。

您还可以尝试 rus-diff https://github.com/mirek/node-rus-diff,它会生成 MongoDB 兼容(重命名/取消设置/设置)差异。

对于您的示例对象:

var person1 = {
  FirstName: "John",
  LastName: "Doh",
  Age: 30,
  EMailAddresses: ["john.doe@gmail.com", "jd@initials.com"],
  Children: [
    {
      FirstName: "Sara",
      LastName: "Doe",
      Age: 2
    }, {
      FirstName: "Beth",
      LastName: "Doe",
      Age: 5
    }
  ]
};

var person2 = {
  FirstName: "John",
  LastName: "Doe",
  Age: 33,
  EMailAddresses: ["john.doe@gmail.com", "jdoe@hotmail.com"],
  Children: [
    {
      FirstName: "Sara",
      LastName: "Doe",
      Age: 3
    }, {
      FirstName: "Bethany",
      LastName: "Doe",
      Age: 5
    }
  ]
};

var rusDiff = require('rus-diff').rusDiff

console.log(rusDiff(person1, person2))

它生成一个集合列表:

{ '$set': 
   { 'Age': 33,
     'Children.0.Age': 3,
     'Children.1.FirstName': 'Bethany',
     'EMailAddresses.1': 'jdoe@hotmail.com',
     'LastName': 'Doe' } }

方案一

使用 JSON.stringify(obj) 获取要比较的对象的字符串表示形式。将字符串保存到文件中。使用任何差异查看器来比较文本文件。

注意:JSON.stringify 将忽略指向函数定义的属性。

解决方案2

这可以做你想做的一些修改,它是函数 _.isEqual ( http://documentcloud.github.com/underscore/ )的修改版本请随时提出任何修改建议!我写它是为了弄清楚两个对象之间的第一个差异发生在哪里。

// Given two objects find the first key or value not matching, algorithm is a
// inspired by of _.isEqual.
function diffObjects(a, b) {
  console.info("---> diffObjects", {"a": a, "b": b});
  // Check object identity.
  if (a === b) return true;
  // Different types?
  var atype = typeof(a), btype = typeof(b);
  if (atype != btype) {
    console.info("Type mismatch:", {"a": a, "b": b});
    return false;
  };
  // Basic equality test (watch out for coercions).
  if (a == b) return true;
  // One is falsy and the other truthy.
  if ((!a && b) || (a && !b)) {
    console.info("One is falsy and the other truthy:", {"a": a, "b": b});
    return false;
  }
  // Unwrap any wrapped objects.
  if (a._chain) a = a._wrapped;
  if (b._chain) b = b._wrapped;
  // One of them implements an isEqual()?
  if (a.isEqual) return a.isEqual(b);
  // Check dates' integer values.
  if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime();
  // Both are NaN?
  if (_.isNaN(a) && _.isNaN(b)) {
    console.info("Both are NaN?:", {"a": a, "b": b});
    return false;
  }
  // Compare regular expressions.
  if (_.isRegExp(a) && _.isRegExp(b))
    return a.source     === b.source &&
           a.global     === b.global &&
           a.ignoreCase === b.ignoreCase &&
           a.multiline  === b.multiline;
  // If a is not an object by this point, we can't handle it.
  if (atype !== 'object') {
    console.info("a is not an object:", {"a": a});
    return false;
  }
  // Check for different array lengths before comparing contents.
  if (a.length && (a.length !== b.length)) {
    console.info("Arrays are of different length:", {"a": a, "b": b});
    return false;
  }
  // Nothing else worked, deep compare the contents.
  var aKeys = _.keys(a), bKeys = _.keys(b);
  // Different object sizes?
  if (aKeys.length != bKeys.length) {
    console.info("Different object sizes:", {"a": a, "b": b});
    return false;
  }
  // Recursive comparison of contents.
  for (var key in a) if (!(key in b) || !diffObjects(a[key], b[key])) return false;
  return true;
};