代码之家  ›  专栏  ›  技术社区  ›  Jason Bunting

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

  •  16
  • Jason Bunting  · 技术社区  · 16 年前

    值得一提的是,这些对象通常以JSON的形式从服务器检索,并且通常不超过几层(即,可能是一组对象本身包含数据,然后是包含其他数据对象的数组)。

    我不仅想看到基本属性的变化,还想看到数组成员数量的差异,等等。

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


    编辑:这些对象通常在结构上彼此非常接近,因此我们不是在讨论彼此完全不同的对象,而是可能有3或4个三角形的对象。

    9 回复  |  直到 9 年前
        1
  •  21
  •   Jason Bunting    16 年前

    这是我的问题的一个局部的、朴素的解决方案——我会在进一步发展的过程中更新它。

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

    this Property 值是指被比较对象的根。因此,这一解决方案还不成熟 确切地 我所需要的,但它非常接近。

    希望这对其他人有用,如果你有任何改进建议,我洗耳恭听;我昨晚很晚才写这封信(即今天凌晨),可能有些事情我完全忽略了。

    谢谢

        2
  •  16
  •   ryanm    9 年前

    在回顾了现有的答案之后,我注意到 https://github.com/flitbit/diff

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

        3
  •  8
  •   fluffy    6 年前

    有一个 objectDiff library 这让你可以这么做。在其上 demo page

        4
  •  6
  •   Mirek Rusin    10 年前

    你也可以试试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' } }
    
        5
  •  5
  •   mozey    13 年前

    解决方案1

    解决方案2

    这可以通过一些修改实现您想要的功能,它是函数u.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;
    };
    
        6
  •  3
  •   astone26    11 年前
        7
  •  1
  •   B T    9 年前

    我最近写了一个模块来实现这一点,因为我不满意我发现的众多不同的模块(我列出了一堆最流行的模块,以及为什么它们在我的模块自述中不被接受)。它叫 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}]}
    ]
    */
    
        8
  •  1
  •   Chololoco    8 年前

    我找到的库都不够,所以我写了自己的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;
    }]);
    
        9
  •  1
  •   Mickael Lherminez hyankov    5 年前

    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>
        10
  •  0
  •   Farouk Rabhi    4 年前

    var first = [ 1, 2, 3, 4, 5 ];
    var second = [ 4, 5, 6 ];
    
    var difference = first.filter(x => second.indexOf(x) === -1);
    console.log(difference);