【问题标题】:How can I get a list of the differences between two JavaScript object graphs?如何获取两个 JavaScript 对象图之间的差异列表?
【发布时间】:2010-09-20 20:03:21
【问题描述】:

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

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

我不仅要查看基本属性的变化,还要查看数组成员数量等方面的差异等。

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


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

【问题讨论】:

  • 杰森,感谢您的支持。尽管如此,我还是要删除我的答案。显然,这不是你所追求的,而且实施是值得商榷的,所以我想保留它没有任何价值。如果您有适合您需求的解决方案,我很想在这里看到它。
  • 我对您决定删除它感到失望,但请尊重您这样做的权利。我只是了解到,有些东西可以从其他人的问题解决方案中学习,即使他们没有解决他们设计的确切问题。我们几乎可以从任何事情中学习。
  • 我在这里给出了非常好的答案:stackoverflow.com/a/71173966/1919821

标签: javascript json data-structures diff


【解决方案1】:

你可以用 filter 和 indexOf 做到这一点

var first = [ 1, 2, 3, 4, 5 ];
var second = [ 4, 5, 6 ];

var difference = first.filter(x => second.indexOf(x) === -1);
console.log(difference);

【讨论】:

    【解决方案2】:

    var d = { 
       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 f = { 
       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 
          } 
       ] 
    };
    
    resultobj = []
    function comp_obj(t1,t2){
    	flag = 1;
    	key1_arr = Object.keys(t1)
    	key2_arr = Object.keys(t2)
    	if(key1_arr.length == key2_arr.length){
    		for(key1 in t1){
    			ty1    = Object.prototype.toString.call(t1[key1])
    			ty2    = Object.prototype.toString.call(t2[key1])
    			if(ty1 == ty2) {
    				if(ty1 == '[object String]' || ty1 == '[object Number]' ){
    					if(t2[key1] != t1[key1]){
    						flag = 0;
    						break;
    					}	
    				}else if(ty1 == '[object Array]'){
    					var result = comp_arr(t1[key1],t2[key1]);
    					console.log(ty1,ty2)
    					if(!result)
    						flag = 0;
    				}else if(ty1  == '[object Object]'){
    					var result = comp_obj(t1[key1],t2[key1])
    					if(!result)
    						flag = 0;
    						
    				}
    			}else{
    				flag = 0;
    				break;	
    			}
    
    		}
    	}else{
    		flag	= 0;
    	}
    	if(flag)
    		return true
    	else
    		return false;
    }
    function comp_arr(a,b){
    	flag = 1;
    	if(a.length == b.length ){
    		for(var i=0,l=a.length;i<l;i++){
    			type1    = Object.prototype.toString.call(a[i])
    			type2    = Object.prototype.toString.call(b[i])
    			if(type1 == type2) {
    				if(type1 == '[object String]' || type1 == '[object Number]' ){
    					if( a[i] != b[i]){
    						flag = 0;
    						break;
    					}	
    				}else if(type1 == '[object Array]'){
    					var result = comp_arr(a[i],b[i]);
    					if(!result)
    						flag = 0;
    				}else if(type1  == '[object Object]'){
    					var result = comp_obj(a[i],b[i])
    					if(!result)
    						flag = 0;
    				}	
    			}else{
    				flag = 0;
    				break;	
    			}
    		}
    	}else
    		 flag = 0;
    	if(flag)
    		return true
    	else
    		return false;
    }
    function create(t,attr,parent_node,innerdata){
    	var dom = document.createElement(t)
    	for(key in attr){
    		dom.setAttribute(key,attr[key])
    	}
    	dom.innerHTML = innerdata;
    	parent_node.appendChild(dom)
    	return dom;
    }
    window.onload = function () {
    	for(key in f){
    		if(!(key in d)) 
    			resultobj.push({'Property_name':key,'f_value':JSON.stringify(f[key]),'d_value':JSON.stringify(d[key])})
    		type1    = Object.prototype.toString.call(f[key])
    		type2    = Object.prototype.toString.call(d[key])
    		if(type1 == type2){
    			if(type1 == '[object String]' || type1 == '[object Number]' ){
    				if(f[key] != d[key])
    					resultobj.push({'Property_name':key,'f_value':JSON.stringify(f[key]),'d_value':JSON.stringify(d[key])})
    			}else if(type1 == '[object Array]'){
    				var result = comp_arr(f[key],d[key]);
    				if(!result)
    					resultobj.push({'Property_name':key,'f_value':JSON.stringify(f[key]),'d_value':JSON.stringify(d[key])})
    			}else if(type1 == '[object Object]'){
    				var result = comp_obj(f[key],d[key])	
    				if(!result)
    					resultobj.push({'Property_name':key,'f_value':JSON.stringify(f[key]),'d_value':JSON.stringify(d[key])})
    			}
    		}else 
    			resultobj.push({'Property_name':key,'f_value':JSON.stringify(f[key]),'d_value':JSON.stringify(d[key])})
    	}
    	var tb = document.getElementById('diff');
    	var s1 = document.getElementById('source1');
    	var s2 = document.getElementById('source2');
    	s1.innerHTML = 'Object 1 :'+ JSON.stringify(f)
    	s2.innerHTML = 'Object 2 :'+JSON.stringify(d)
    	resultobj.forEach(function(data,i){
    			tr_dom = create('tr',{},tb,'')
    			no = create('td',{},tr_dom,i+1)
    			Object.keys(data).forEach(function(tr){
    				td_dom = create('td',{},tr_dom,data[tr])
    			})
    	})
    }
      <html>
        <body>
          <p id="source1"> </p>
          <p id="source2"> </p>
          <p id="source7"> DIFFERENCE TABLE</p>
          <table border=''>
            <thead>
              <th>S.no</th>
              <th>Name Of the Key</th>
              <th>Object1 Value</th>
              <th>Object2 Value</th>
            </thead>
            <tbody id="diff">
    
            </tbody>
          </table>
        </body>
    </html>

    【讨论】:

      【解决方案3】:

      有一个objectDiff library 允许您这样做。在其demo page 上,您可以看到两个 javascript 对象之间的区别。

      【讨论】:

        【解决方案4】:

        我找到的库都不够用,所以我编写了自己的 AngularJS 工厂。它以两种方式比较对象并仅返回同一结构内的差异。

        /**
         * Diff
         * Original author: Danny Coulombe
         * Creation date: 2016-05-18
         * 
         * Work with objects to find their differences.
         */
        controllers.factory('diff', [function() {
        
            var factory = {
        
                /**
                 * Compare the original object with the modified one and return their differences.
                 * 
                 * @param original: Object
                 * @param modified: Object
                 * 
                 * @return Object
                 */
                getDifferences: function(original, modified) {
        
                    var type = modified.constructor === Array ? [] : {};
                    var result = angular.copy(type);
                    var comparisons = [[original, modified, 1], [modified, original, 0]];
        
                    comparisons.forEach(function(comparison) {
        
                        angular.forEach(comparison[0], function(value, key) {
        
                            if(result[key] === undefined) {
        
                                if(comparison[1][key] !== undefined && value !==    null && comparison[1][key] !== null && [Object, Array].indexOf(comparison[1][key].constructor) !== -1) {
        
                                    result[key] = factory.getDifferences(value, comparison[1][key]);
                                }
                                else if(comparison[1][key] !== value) {
        
                                    result[key] = comparison[comparison[2]][key];
                                }
        
                                if(angular.equals(type, result[key])
                                || result[key] === undefined
                                || (
                                    comparison[0][key] !== undefined
                                    && result[key] !== null
                                    && comparison[0][key] !== null
                                    && comparison[0][key].length === comparison[1][key].length
                                    && result[key].length === 0
                                )) {
                                    delete result[key];
                                }
                            }
                        });
                    });
        
                    return result;
                }
            };
        
            return factory;
        }]);
        

        【讨论】:

        • 当我运行它时,我得到“源未定义”。 “原始”应该是“来源”吗?另外,“fromObjects”在哪里定义?
        • 对不起,我在写这个答案时更改了一些变量名。 “source”是“original”,“fromObjects”是“getDifferences”。我更新了上面的脚本。
        【解决方案5】:

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

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

        【讨论】:

        • 我把我自己的官方答案改成了你的,因为这是一个老问题了,自从我 7 年前写它以来,JavaScript 的世界已经发生了巨大的变化! :)
        • 是的,JS 的世界已经发生了翻天覆地的变化。我从没想过我会用 JavaScript 编写后端程序!谢谢,@JasonBunting
        【解决方案6】:

        我最近编写了一个模块来执行此操作,因为我对我发现的众多不同模块不满意(我列出了一堆最流行的模块以及为什么它们在我的模块的自述文件中不可接受)。它称为 odiff: https://github.com/Tixit/odiff 。这是一个例子:

        var a = [{a:1,b:2,c:3},              {x:1,y: 2, z:3},              {w:9,q:8,r:7}]
        var b = [{a:1,b:2,c:3},{t:4,y:5,u:6},{x:1,y:'3',z:3},{t:9,y:9,u:9},{w:9,q:8,r:7}]
        
        var diffs = odiff(a,b)
        
        /* diffs now contains:
        [{type: 'add', path:[], index: 2, vals: [{t:9,y:9,u:9}]},
         {type: 'set', path:[1,'y'], val: '3'},
         {type: 'add', path:[], index: 1, vals: [{t:4,y:5,u:6}]}
        ]
        */
        

        【讨论】:

          【解决方案7】:

          您也可以尝试 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' } }
          

          【讨论】:

            【解决方案8】:

            如果你使用的是 NodeJS,这个脚本也有一个 NPM 版本。 https://github.com/NV/objectDiff.js

            欢喜。

            【讨论】:

              【解决方案9】:

              解决方案 1

              使用 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;
              };
              

              【讨论】:

                【解决方案10】:

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

                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"
                   }
                ]
                

                Property 值中的this 指的是被比较对象的根。所以,这个解决方案还不是完全我需要的,但它非常接近。

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

                谢谢。

                【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2014-03-26
                • 2011-03-28
                • 2013-04-12
                • 1970-01-01
                • 1970-01-01
                • 2022-01-04
                相关资源
                最近更新 更多