How can I get a list of differences between two JavaScript graphics?

I want to get a list of all the differences between two JavaScript object graphs with property names and values ​​in which the delta occurs.

For what it is necessary, these objects are usually retrieved from the server as JSON and usually comprise no more than several layers (i.e. it can be an array of objects that themselves have data, and then arrays with other data objects).

I want to not only see changes in the basic properties, but also differences in the number of array members, etc. etc.

If I do not get an answer, I will probably end up writing it myself, but I hope that someone has already done this work or recognized someone who has it.




EDIT: These objects are usually very close in structure to each other, so we are not talking about objects that are completely different from each other, but can have 3 or 4 deltas.

+16
json javascript diff data-structures
Nov 05 '08 at 5:53
source share
9 answers

After reviewing the existing answers, I noticed that the https://github.com/flitbit/diff library is not yet listed as a solution.

From my research, this library, apparently, is the best in terms of active development, contributions and forks to solve the problem of various objects. This is very convenient for creating diffs on the server side and passing only the changed bits to the client.

+12
Jul 16 '15 at 15:26
source share

Here is a partial, naive solution to my problem - I will update it when I develop it.

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

And here is an example of how it will be used, and the data that it will provide (please excuse the long example, but I want to use something relatively non-trivial):

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

At this point, the differences array will look if you serialized it to 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" } ] 

The value of this in the Property value refers to the root of the object that was mapped. So, this solution is not quite what I need yet, but it is pretty close.

Hope this is useful to someone there, and if you have suggestions for improvement, I’m all ears; I wrote this very late last night (that is, early in the morning), and there may be things that I completely ignore.

Thank.

+19
Nov 05 '08 at 19:36
source share

There is a github.com/NV/objectDiff.js library that allows you to do this. On your demo page, you can see the difference between the two javascript objects.

+7
Jul 08 2018-11-11T00:
source share

You can also try rus-diff https://github.com/mirek/node-rus-diff , which generates MongoDB compatibility (rename / uninstall / install).

For the objects of your example:

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

It generates a list of sets:

 { '$set': { 'Age': 33, 'Children.0.Age': 3, 'Children.1.FirstName': 'Bethany', 'EMailAddresses.1': 'jdoe@hotmail.com', 'LastName': 'Doe' } } 
+5
Mar 11 '14 at 20:07
source share

Solution 1

Use JSON.stringify (obj) to get a string representation of the objects you want to compare. Save the string in a file. Use any difference viewer to compare text files.

Note. JSON.stringify ignores properties that point to function definitions.

Decision 2

This can do what you want with some modification, this is a modified version of the _.isEqual function ( http://documentcloud.github.com/underscore/ ). Feel free to suggest any changes! I wrote it to find out where the first difference between the two objects occurs.

 // 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
Jul 08 2018-11-11T00:
source share

This script also has an NPM version if you are using NodeJS. https://github.com/NV/objectDiff.js

Rejoice.

+3
Mar 24 '13 at 11:01
source share

I recently wrote a module for this because I was not happy with the many different modules I found (I listed a bunch of the most popular modules and why they were not acceptable in the readme of my module). It is called odiff : https://github.com/Tixit/odiff . Here is an example:

 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
Jul 06 '15 at 20:27
source share

None of the libraries found were sufficient, so I wrote my own AngularJS factory. It compares objects in both directions and returns only the difference within the same structure.

 /** * 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
May 18 '16 at 19:18
source share

 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> 
0
Nov 10 '17 at 6:04 on
source share



All Articles