Jak Mogę uzyskać listę różnic między dwoma obiektowymi wykresami JavaScript?

Chcę być w stanie uzyskać listę wszystkich różnic między dwoma wykresami obiektów JavaScript, z nazwami właściwości i wartościami, w których występują delty.

Dla tego, co jest warte, obiekty te są zwykle pobierane z serwera jako JSON i zazwyczaj mają nie więcej niż garść warstw głębokich (tzn. może to być tablica obiektów, które same mają dane, a następnie tablice z innymi obiektami danych).

Chcę zobaczyć nie tylko zmiany podstawowych właściwości, ale różnice w liczba członków tablicy, itp. itd.

Jeśli nie dostanę odpowiedzi, prawdopodobnie skończę to pisać sam, ale mam nadzieję, że ktoś już zrobił tę pracę lub zna kogoś, kto to zrobił.


EDIT: obiekty te zazwyczaj będą bardzo blisko siebie, więc nie mówimy o obiektach, które są zupełnie różne od siebie, ale mogą mieć 3 lub 4 delty.

Author: Brian Tompsett - 汤莱恩, 2008-11-05

9 answers

Po przejrzeniu istniejących odpowiedzi zauważyłem, że https://github.com/flitbit/diff biblioteka nie została jeszcze wymieniona jako rozwiązanie.

Z moich badań wynika, że biblioteka ta wydaje się najlepsza pod względem aktywnego rozwoju, wkładu i widelców do rozwiązywania problemów związanych z różnymi obiektami. Jest to bardzo przydatne przy tworzeniu różnic po stronie serwera i przekazywaniu klientowi tylko zmienionych bitów.

 13
Author: ryanm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-16 15:26:24

Oto częściowe, naiwne rozwiązanie mojego problemu - zaktualizuję go w miarę dalszego rozwoju.

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

A oto przykład jak to będzie używane i dane, które dostarczy (proszę wybaczyć długi przykład, ale chcę użyć czegoś stosunkowo nietrywialnego): {]}

var person1 = { 
   FirstName : "John", 
   LastName : "Doh", 
   Age : 30, 
   EMailAddresses : [
      "[email protected]", 
      "[email protected]"
   ], 
   Children : [ 
      { 
         FirstName : "Sara", 
         LastName : "Doe", 
         Age : 2 
      }, { 
         FirstName : "Beth", 
         LastName : "Doe", 
         Age : 5 
      } 
   ] 
};

var person2 = { 
   FirstName : "John", 
   LastName : "Doe", 
   Age : 33, 
   EMailAddresses : [
      "[email protected]", 
      "[email protected]"
   ], 
   Children : [ 
      { 
         FirstName : "Sara", 
         LastName : "Doe", 
         Age : 3 
      }, { 
         FirstName : "Bethany", 
         LastName : "Doe", 
         Age : 5 
      } 
   ] 
};

var differences = findDifferences(person1, person2);

W tym momencie, oto jak wyglądałaby tablica differences, Jeśli serializowałbyś ją do JSON:

[
   {
      "Property":"this.LastName", 
      "ObjectA":"Doh", 
      "ObjectB":"Doe"
   }, {
      "Property":"this.Age", 
      "ObjectA":30, 
      "ObjectB":33
   }, {
      "Property":"this.EMailAddresses[1]", 
      "ObjectA":"[email protected]", 
      "ObjectB":"[email protected]"
   }, {
      "Property":"this.Children[0].Age", 
      "ObjectA":2, 
      "ObjectB":3
   }, {
      "Property":"this.Children[1].FirstName", 
      "ObjectA":"Beth", 
      "ObjectB":"Bethany"
   }
]

Wartość this w Property odnosi się do katalogu głównego porównywanego obiektu. Więc to rozwiązanie nie jest jeszcze dokładnie tym, czego potrzebuję, ale jest cholernie blisko.

Mam nadzieję, że jest to przydatne dla kogoś tam, a jeśli masz jakieś sugestie dotyczące poprawy, jestem za uszy; napisałem to bardzo późno w nocy (tj. wcześnie rano) i mogą być rzeczy, które całkowicie pomijam.

Dzięki.
 19
Author: Jason Bunting,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-06 16:15:18

Istnieje biblioteka objectDiff , która pozwala ci to zrobić. Na stronie demonstracyjnej widać różnicę między dwoma obiektami javascript.

 8
Author: anton_byrna,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-09 20:40:08

Możesz również spróbować rus-diff https://github.com/mirek/node-rus-diff który generuje diff zgodny z MongoDB (rename/unset/set).

Dla przykładowych obiektów:

var person1 = {
  FirstName: "John",
  LastName: "Doh",
  Age: 30,
  EMailAddresses: ["[email protected]", "[email protected]"],
  Children: [
    {
      FirstName: "Sara",
      LastName: "Doe",
      Age: 2
    }, {
      FirstName: "Beth",
      LastName: "Doe",
      Age: 5
    }
  ]
};

var person2 = {
  FirstName: "John",
  LastName: "Doe",
  Age: 33,
  EMailAddresses: ["[email protected]", "[email protected]"],
  Children: [
    {
      FirstName: "Sara",
      LastName: "Doe",
      Age: 3
    }, {
      FirstName: "Bethany",
      LastName: "Doe",
      Age: 5
    }
  ]
};

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

console.log(rusDiff(person1, person2))

Generuje listę zestawów:

{ '$set': 
   { 'Age': 33,
     'Children.0.Age': 3,
     'Children.1.FirstName': 'Bethany',
     'EMailAddresses.1': '[email protected]',
     'LastName': 'Doe' } }
 5
Author: Mirek Rusin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-11 20:07:11

Rozwiązanie 1

Użyj JSON.stringify (obj), aby uzyskać reprezentację łańcuchową obiektów, które chcesz porównać. Zapisz łańcuch do pliku. Użyj dowolnej przeglądarki różnic do porównania plików tekstowych.

Uwaga: JSON.stringify zignoruje właściwości wskazujące na Definicje funkcji.

Rozwiązanie 2

To może zrobić to, co chcesz z jakąś modyfikacją, jest to zmodyfikowana wersja funkcji _.isEqual ( http://documentcloud.github.com/underscore/). zapraszam do sugerowania wszelkich modyfikacji! Napisałem go, żeby dowiedzieć się, gdzie występuje pierwsza różnica między dwoma obiektami.

// 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;
};
 4
Author: mozey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-08 10:16:35

Ten skrypt ma również wersję NPM, jeśli używasz NodeJS. https://github.com/NV/objectDiff.js

Radujcie się.

 3
Author: Lander,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-24 11:01:34

Niedawno napisałem moduł, aby to zrobić, ponieważ nie byłem zadowolony z wielu różnych modułów, które znalazłem(wymieniłem kilka najpopularniejszych modułów i dlaczego nie były akceptowalne w readme mojego modułu). Nazywa się odiff: https://github.com/Tixit/odiff . Oto przykład:

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}]}
]
*/
 1
Author: B T,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-06 20:27:10

var d = { 
   FirstName : "John", 
   LastName : "Doh", 
   Age : 30, 
   EMailAddresses : [
      "[email protected]", 
      "[email protected]"
   ], 
   Children : [ 
      { 
         FirstName : "Sara", 
         LastName : "Doe", 
         Age : 2 
      }, { 
         FirstName : "Beth", 
         LastName : "Doe", 
         Age : 5 
      } 
   ] 
};

var f = { 
   FirstName : "John", 
   LastName : "Doe", 
   Age : 33, 
   EMailAddresses : [
      "[email protected]", 
      "[email protected]"
   ], 
   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>
 1
Author: Sudhakar Ayyar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-10 06:04:50

Żadna z bibliotek, które znalazłem, nie była wystarczająca, więc napisałem własną fabrykę AngularJS. Porównuje obiekty w obie strony i zwraca tylko różnicę w obrębie tej samej struktury.

/**
 * 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;
}]);
 0
Author: Danny Coulombe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-24 18:05:04