Generic deep diff between two objects - javascript
I have two objects: oldObj and newObj.
The data in oldObj was used to populate a form and newObj is the result of the user changing data in this form and submitting it.
Both objects are deep, ie. they have properties that are objects or arrays of objects etc - they can be n levels deep, thus the diff algorithm needs to be recursive.
Now I need to not just figure out what was changed (as in added/updated/deleted) from oldObj to newObj, but also how to best represent it.
So far my thoughts was to just build a genericDeepDiffBetweenObjects method that would return an object on the form {add:{...},upd:{...},del:{...}} but then I thought: somebody else must have needed this before.
So... does anyone know of a library or a piece of code that will do this and maybe have an even better way of representing the difference (in a way that is still JSON serializable)?
Update:
I have thought of a better way to represent the updated data, by using the same object structure as newObj, but turning all property values into objects on the form:
{type: '<update|create|delete>', data: <propertyValue>}
So if newObj.prop1 = 'new value' and oldObj.prop1 = 'old value' it would set returnObj.prop1 = {type: 'update', data: 'new value'}
Update 2:
It gets truely hairy when we get to properties that are arrays, since the array [1,2,3] should be counted as equal to [2,3,1], which is simple enough for arrays of value based types like string, int & bool, but gets really difficult to handle when it comes to arrays of reference types like objects and arrays.
Example arrays that should be found equal:
[1,[{c: 1},2,3],{a:'hey'}] and [{a:'hey'},1,[3,{c: 1},2]]
Not only is it quite complex to check for this type of deep value equality, but also to figure out a good way to represent the changes that might be.
I wrote a little class that is doing what you want, you can test it here.
Only thing that is different from your proposal is that I don't consider
[1,[{c: 1},2,3],{a:'hey'}]
and
[{a:'hey'},1,[3,{c: 1},2]]
to be same, because I think that arrays are not equal if order of their elements is not same. Of course this can be changed if needed. Also this code can be further enhanced to take function as argument that will be used to format diff object in arbitrary way based on passed primitive values (now this job is done by "compareValues" method).
var deepDiffMapper = function () {
return {
VALUE_CREATED: 'created',
VALUE_UPDATED: 'updated',
VALUE_DELETED: 'deleted',
VALUE_UNCHANGED: 'unchanged',
map: function(obj1, obj2) {
if (this.isFunction(obj1) || this.isFunction(obj2)) {
throw 'Invalid argument. Function given, object expected.';
}
if (this.isValue(obj1) || this.isValue(obj2)) {
return {
type: this.compareValues(obj1, obj2),
data: obj1 === undefined ? obj2 : obj1
};
}
var diff = {};
for (var key in obj1) {
if (this.isFunction(obj1[key])) {
continue;
}
var value2 = undefined;
if (obj2[key] !== undefined) {
value2 = obj2[key];
}
diff[key] = this.map(obj1[key], value2);
}
for (var key in obj2) {
if (this.isFunction(obj2[key]) || diff[key] !== undefined) {
continue;
}
diff[key] = this.map(undefined, obj2[key]);
}
return diff;
},
compareValues: function (value1, value2) {
if (value1 === value2) {
return this.VALUE_UNCHANGED;
}
if (this.isDate(value1) && this.isDate(value2) && value1.getTime() === value2.getTime()) {
return this.VALUE_UNCHANGED;
}
if (value1 === undefined) {
return this.VALUE_CREATED;
}
if (value2 === undefined) {
return this.VALUE_DELETED;
}
return this.VALUE_UPDATED;
},
isFunction: function (x) {
return Object.prototype.toString.call(x) === '[object Function]';
},
isArray: function (x) {
return Object.prototype.toString.call(x) === '[object Array]';
},
isDate: function (x) {
return Object.prototype.toString.call(x) === '[object Date]';
},
isObject: function (x) {
return Object.prototype.toString.call(x) === '[object Object]';
},
isValue: function (x) {
return !this.isObject(x) && !this.isArray(x);
}
}
}();
var result = deepDiffMapper.map({
a: 'i am unchanged',
b: 'i am deleted',
e: {
a: 1,
b: false,
c: null
},
f: [1, {
a: 'same',
b: [{
a: 'same'
}, {
d: 'delete'
}]
}],
g: new Date('2017.11.25')
}, {
a: 'i am unchanged',
c: 'i am created',
e: {
a: '1',
b: '',
d: 'created'
},
f: [{
a: 'same',
b: [{
a: 'same'
}, {
c: 'create'
}]
}, 1],
g: new Date('2017.11.25')
});
console.log(result);
Using Underscore, a simple diff:
var o1 = {a: 1, b: 2, c: 2},
o2 = {a: 2, b: 1, c: 2};
_.omit(o1, function(v,k) { return o2[k] === v; })
Results in the parts of o1 that correspond but with different values in o2:
{a: 1, b: 2}
It'd be different for a deep diff:
function diff(a,b) {
var r = {};
_.each(a, function(v,k) {
if(b[k] === v) return;
// but what if it returns an empty object? still attach?
r[k] = _.isObject(v)
? _.diff(v, b[k])
: v
;
});
return r;
}
As pointed out by #Juhana in the comments, the above is only a diff a-->b and not reversible (meaning extra properties in b would be ignored). Use instead a-->b-->a:
(function(_) {
function deepDiff(a, b, r) {
_.each(a, function(v, k) {
// already checked this or equal...
if (r.hasOwnProperty(k) || b[k] === v) return;
// but what if it returns an empty object? still attach?
r[k] = _.isObject(v) ? _.diff(v, b[k]) : v;
});
}
/* the function */
_.mixin({
diff: function(a, b) {
var r = {};
deepDiff(a, b, r);
deepDiff(b, a, r);
return r;
}
});
})(_.noConflict());
See http://jsfiddle.net/drzaus/9g5qoxwj/ for full example+tests+mixins
I'd like to offer an ES6 solution...This is a one-way diff, meaning that it will return keys/values from o2 that are not identical to their counterparts in o1:
let o1 = {
one: 1,
two: 2,
three: 3
}
let o2 = {
two: 2,
three: 3,
four: 4
}
let diff = Object.keys(o2).reduce((diff, key) => {
if (o1[key] === o2[key]) return diff
return {
...diff,
[key]: o2[key]
}
}, {})
Here is a JavaScript library which can be used for finding diff between two JavaScript objects:
Github URL:
https://github.com/cosmicanant/recursive-diff
Npmjs url: https://www.npmjs.com/package/recursive-diff
recursive-diff library can be used in the browser as well as in a Node.js based server side application. For browser, it can be used as below:
<script type="text" src="https://unpkg.com/recursive-diff#latest/dist/recursive-diff.min.js"/>
<script type="text/javascript">
const ob1 = {a:1, b: [2,3]};
const ob2 = {a:2, b: [3,3,1]};
const delta = recursiveDiff.getDiff(ob1,ob2);
/* console.log(delta) will dump following data
[
{path: ['a'], op: 'update', val: 2}
{path: ['b', '0'], op: 'update',val: 3},
{path: ['b',2], op: 'add', val: 1 },
]
*/
const ob3 = recursiveDiff.applyDiff(ob1, delta); //expect ob3 is deep equal to ob2
</script>
Whereas in a node.js based application it can be used as below:
const diff = require('recursive-diff');
const ob1 = {a: 1}, ob2: {b:2};
const diff = diff.getDiff(ob1, ob2);
Using Lodash:
_.mergeWith(oldObj, newObj, function (objectValue, sourceValue, key, object, source) {
if ( !(_.isEqual(objectValue, sourceValue)) && (Object(objectValue) !== objectValue)) {
console.log(key + "\n Expected: " + sourceValue + "\n Actual: " + objectValue);
}
});
I don't use key/object/source but I left it in there if you need to access them.
The object comparison just prevents the console from printing the differences to the console from the outermost element to the innermost element.
You can add some logic inside to handle arrays. Perhaps sort the arrays first. This is a very flexible solution.
EDIT
Changed from _.merge to _.mergeWith due to lodash update. Thanks Aviron for noticing the change.
const diff = require("deep-object-diff").diff;
let differences = diff(obj2, obj1);
There is an npm module with over 500k weekly downloads: https://www.npmjs.com/package/deep-object-diff
I like the object like representation of the differences - especially it is easy to see the structure, when it is formated.
const diff = require("deep-object-diff").diff;
const lhs = {
foo: {
bar: {
a: ['a', 'b'],
b: 2,
c: ['x', 'y'],
e: 100 // deleted
}
},
buzz: 'world'
};
const rhs = {
foo: {
bar: {
a: ['a'], // index 1 ('b') deleted
b: 2, // unchanged
c: ['x', 'y', 'z'], // 'z' added
d: 'Hello, world!' // added
}
},
buzz: 'fizz' // updated
};
console.log(diff(lhs, rhs)); // =>
/*
{
foo: {
bar: {
a: {
'1': undefined
},
c: {
'2': 'z'
},
d: 'Hello, world!',
e: undefined
}
},
buzz: 'fizz'
}
*/
Here is a solution that is:
TypeScript (but easily convertible to JavaScript)
have no lib dependencies
generic, and doesn't care about checking object types (aside the object type)
supports properties with value undefined
deep of not (default)
First we define the comparison result interface:
export interface ObjectDiff {
added: {} | ObjectDiff;
updated: {
[propName: string]: Update | ObjectDiff;
};
removed: {} | ObjectDiff;
unchanged: {} | ObjectDiff;
}
with the special case of change where we want to know what are old and new values:
export interface Update {
oldValue: any;
newValue: any;
}
Then we can provide the diff function which is merely two loops (with recursivity if deep is true):
export class ObjectUtils {
/**
* #return if obj is an Object, including an Array.
*/
static isObject(obj: any) {
return obj !== null && typeof obj === 'object';
}
/**
* #param oldObj The previous Object or Array.
* #param newObj The new Object or Array.
* #param deep If the comparison must be performed deeper than 1st-level properties.
* #return A difference summary between the two objects.
*/
static diff(oldObj: {}, newObj: {}, deep = false): ObjectDiff {
const added = {};
const updated = {};
const removed = {};
const unchanged = {};
for (const oldProp in oldObj) {
if (oldObj.hasOwnProperty(oldProp)) {
const newPropValue = newObj[oldProp];
const oldPropValue = oldObj[oldProp];
if (newObj.hasOwnProperty(oldProp)) {
if (newPropValue === oldPropValue) {
unchanged[oldProp] = oldPropValue;
} else {
updated[oldProp] = deep && this.isObject(oldPropValue) && this.isObject(newPropValue) ? this.diff(oldPropValue, newPropValue, deep) : {newValue: newPropValue};
}
} else {
removed[oldProp] = oldPropValue;
}
}
}
for (const newProp in newObj) {
if (newObj.hasOwnProperty(newProp)) {
const oldPropValue = oldObj[newProp];
const newPropValue = newObj[newProp];
if (oldObj.hasOwnProperty(newProp)) {
if (oldPropValue !== newPropValue) {
if (!deep || !this.isObject(oldPropValue)) {
updated[newProp].oldValue = oldPropValue;
}
}
} else {
added[newProp] = newPropValue;
}
}
}
return {added, updated, removed, unchanged};
}
}
As an example, calling:
ObjectUtils.diff(
{
a: 'a',
b: 'b',
c: 'c',
arr: ['A', 'B'],
obj: {p1: 'p1', p2: 'p2'}
},
{
b: 'x',
c: 'c',
arr: ['B', 'C'],
obj: {p2: 'p2', p3: 'p3'},
d: 'd'
},
);
will return:
{
added: {d: 'd'},
updated: {
b: {oldValue: 'b', newValue: 'x'},
arr: {oldValue: ['A', 'B'], newValue: ['B', 'C']},
obj: {oldValue: {p1: 'p1', p2: 'p2'}, newValue: {p2: 'p2', p3: 'p3'}}
},
removed: {a: 'a'},
unchanged: {c: 'c'},
}
and calling the same with the deep third parameter will return:
{
added: {d: 'd'},
updated: {
b: {oldValue: 'b', newValue: 'x'},
arr: {
added: {},
removed: {},
unchanged: {},
updated: {
0: {oldValue: 'A', newValue: 'B'},
1: {oldValue: 'B', newValue: 'C', }
}
},
obj: {
added: {p3: 'p3'},
removed: {p1: 'p1'},
unchanged: {p2: 'p2'},
updated: {}
}
},
removed: {a: 'a'},
unchanged: {c: 'c'},
}
I modified #sbgoran's answer so that the resulting diff object includes only the changed values, and omits values that were the same. In addition, it shows both the original value and the updated value.
var deepDiffMapper = function () {
return {
VALUE_CREATED: 'created',
VALUE_UPDATED: 'updated',
VALUE_DELETED: 'deleted',
VALUE_UNCHANGED: '---',
map: function (obj1, obj2) {
if (this.isFunction(obj1) || this.isFunction(obj2)) {
throw 'Invalid argument. Function given, object expected.';
}
if (this.isValue(obj1) || this.isValue(obj2)) {
let returnObj = {
type: this.compareValues(obj1, obj2),
original: obj1,
updated: obj2,
};
if (returnObj.type != this.VALUE_UNCHANGED) {
return returnObj;
}
return undefined;
}
var diff = {};
let foundKeys = {};
for (var key in obj1) {
if (this.isFunction(obj1[key])) {
continue;
}
var value2 = undefined;
if (obj2[key] !== undefined) {
value2 = obj2[key];
}
let mapValue = this.map(obj1[key], value2);
foundKeys[key] = true;
if (mapValue) {
diff[key] = mapValue;
}
}
for (var key in obj2) {
if (this.isFunction(obj2[key]) || foundKeys[key] !== undefined) {
continue;
}
let mapValue = this.map(undefined, obj2[key]);
if (mapValue) {
diff[key] = mapValue;
}
}
//2020-06-13: object length code copied from https://stackoverflow.com/a/13190981/2336212
if (Object.keys(diff).length > 0) {
return diff;
}
return undefined;
},
compareValues: function (value1, value2) {
if (value1 === value2) {
return this.VALUE_UNCHANGED;
}
if (this.isDate(value1) && this.isDate(value2) && value1.getTime() === value2.getTime()) {
return this.VALUE_UNCHANGED;
}
if (value1 === undefined) {
return this.VALUE_CREATED;
}
if (value2 === undefined) {
return this.VALUE_DELETED;
}
return this.VALUE_UPDATED;
},
isFunction: function (x) {
return Object.prototype.toString.call(x) === '[object Function]';
},
isArray: function (x) {
return Object.prototype.toString.call(x) === '[object Array]';
},
isDate: function (x) {
return Object.prototype.toString.call(x) === '[object Date]';
},
isObject: function (x) {
return Object.prototype.toString.call(x) === '[object Object]';
},
isValue: function (x) {
return !this.isObject(x) && !this.isArray(x);
}
}
}();
These days, there are quite a few modules available for this. I recently wrote a module to do this, because I wasn't satisfied with the numerous diffing modules I found. Its called odiff: https://github.com/Tixit/odiff . I also listed a bunch of the most popular modules and why they weren't acceptable in the readme of odiff, which you could take a look through if odiff doesn't have the properties you want. Here's 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}]}
]
*/
I know I'm late to the party, but I needed something similar that the above answers didn't help.
I was using Angular's $watch function to detect changes in a variable. Not only did I need to know whether a property had changed on the variable, but I also wanted to make sure that the property that changed was not a temporary, calculated field. In other words, I wanted to ignore certain properties.
Here's the code:
function diff(obj1,obj2,exclude) {
var r = {};
if (!exclude) exclude = [];
for (var prop in obj1) {
if (obj1.hasOwnProperty(prop) && prop != '__proto__') {
if (exclude.indexOf(obj1[prop]) == -1) {
// check if obj2 has prop
if (!obj2.hasOwnProperty(prop)) r[prop] = obj1[prop];
// check if prop is object and
// NOT a JavaScript engine object (i.e. __proto__), if so, recursive diff
else if (obj1[prop] === Object(obj1[prop])) {
var difference = diff(obj1[prop], obj2[prop]);
if (Object.keys(difference).length > 0) r[prop] = difference;
}
// check if obj1 and obj2 are equal
else if (obj1[prop] !== obj2[prop]) {
if (obj1[prop] === undefined)
r[prop] = 'undefined';
if (obj1[prop] === null)
r[prop] = null;
else if (typeof obj1[prop] === 'function')
r[prop] = 'function';
else if (typeof obj1[prop] === 'object')
r[prop] = 'object';
else
r[prop] = obj1[prop];
}
}
}
}
return r;
}
https://jsfiddle.net/rv01x6jo/
Here's how to use it:
// To only return the difference
var difference = diff(newValue, oldValue);
// To exclude certain properties
var difference = diff(newValue, oldValue, [newValue.prop1, newValue.prop2, newValue.prop3]);
Hope this helps someone.
Another lodash based solution which is a bit specific to the case when we want to see the diff for the update made to an object:
const diff = return {
old: _.pickBy(oldObject, (value, key) => { return !_.isEqual(value, newObject[key]); }),
new: _.pickBy(newObject, (value, key) => { return !_.isEqual(oldObject[key], value); })
}
Didn't use _.omitBy because of performance implications.
I've developed the Function named "compareValue()" in Javascript.
it returns whether the value is same or not.
I've called compareValue() in for loop of one Object.
you can get difference of two objects in diffParams.
var diffParams = {};
var obj1 = {"a":"1", "b":"2", "c":[{"key":"3"}]},
obj2 = {"a":"1", "b":"66", "c":[{"key":"55"}]};
for( var p in obj1 ){
if ( !compareValue(obj1[p], obj2[p]) ){
diffParams[p] = obj1[p];
}
}
function compareValue(val1, val2){
var isSame = true;
for ( var p in val1 ) {
if (typeof(val1[p]) === "object"){
var objectValue1 = val1[p],
objectValue2 = val2[p];
for( var value in objectValue1 ){
isSame = compareValue(objectValue1[value], objectValue2[value]);
if( isSame === false ){
return false;
}
}
}else{
if(val1 !== val2){
isSame = false;
}
}
}
return isSame;
}
console.log(diffParams);
I composed this for my own use-case (es5 environment), thought this might be useful for someone, so here it is:
function deepCompare(obj1, obj2) {
var diffObj = Array.isArray(obj2) ? [] : {}
Object.getOwnPropertyNames(obj2).forEach(function(prop) {
if (typeof obj2[prop] === 'object') {
diffObj[prop] = deepCompare(obj1[prop], obj2[prop])
// if it's an array with only length property => empty array => delete
// or if it's an object with no own properties => delete
if (Array.isArray(diffObj[prop]) && Object.getOwnPropertyNames(diffObj[prop]).length === 1 || Object.getOwnPropertyNames(diffObj[prop]).length === 0) {
delete diffObj[prop]
}
} else if(obj1[prop] !== obj2[prop]) {
diffObj[prop] = obj2[prop]
}
});
return diffObj
}
This might be not really efficient, but will output an object with only different props based on second Obj.
Here is a typescript version of #sbgoran code
export class deepDiffMapper {
static VALUE_CREATED = 'created';
static VALUE_UPDATED = 'updated';
static VALUE_DELETED = 'deleted';
static VALUE_UNCHANGED ='unchanged';
protected isFunction(obj: object) {
return {}.toString.apply(obj) === '[object Function]';
};
protected isArray(obj: object) {
return {}.toString.apply(obj) === '[object Array]';
};
protected isObject(obj: object) {
return {}.toString.apply(obj) === '[object Object]';
};
protected isDate(obj: object) {
return {}.toString.apply(obj) === '[object Date]';
};
protected isValue(obj: object) {
return !this.isObject(obj) && !this.isArray(obj);
};
protected compareValues (value1: any, value2: any) {
if (value1 === value2) {
return deepDiffMapper.VALUE_UNCHANGED;
}
if (this.isDate(value1) && this.isDate(value2) && value1.getTime() === value2.getTime()) {
return deepDiffMapper.VALUE_UNCHANGED;
}
if ('undefined' == typeof(value1)) {
return deepDiffMapper.VALUE_CREATED;
}
if ('undefined' == typeof(value2)) {
return deepDiffMapper.VALUE_DELETED;
}
return deepDiffMapper.VALUE_UPDATED;
}
public map(obj1: object, obj2: object) {
if (this.isFunction(obj1) || this.isFunction(obj2)) {
throw 'Invalid argument. Function given, object expected.';
}
if (this.isValue(obj1) || this.isValue(obj2)) {
return {
type: this.compareValues(obj1, obj2),
data: (obj1 === undefined) ? obj2 : obj1
};
}
var diff = {};
for (var key in obj1) {
if (this.isFunction(obj1[key])) {
continue;
}
var value2 = undefined;
if ('undefined' != typeof(obj2[key])) {
value2 = obj2[key];
}
diff[key] = this.map(obj1[key], value2);
}
for (var key in obj2) {
if (this.isFunction(obj2[key]) || ('undefined' != typeof(diff[key]))) {
continue;
}
diff[key] = this.map(undefined, obj2[key]);
}
return diff;
}
}
Here is a modified version of something found on gisthub.
isNullBlankOrUndefined = function (o) {
return (typeof o === "undefined" || o == null || o === "");
}
/**
* Deep diff between two object, using lodash
* #param {Object} object Object compared
* #param {Object} base Object to compare with
* #param {Object} ignoreBlanks will not include properties whose value is null, undefined, etc.
* #return {Object} Return a new object who represent the diff
*/
objectDifference = function (object, base, ignoreBlanks = false) {
if (!lodash.isObject(object) || lodash.isDate(object)) return object // special case dates
return lodash.transform(object, (result, value, key) => {
if (!lodash.isEqual(value, base[key])) {
if (ignoreBlanks && du.isNullBlankOrUndefined(value) && isNullBlankOrUndefined( base[key])) return;
result[key] = lodash.isObject(value) && lodash.isObject(base[key]) ? objectDifference(value, base[key]) : value;
}
});
}
I have used this piece of code for doing the task that you describe:
function mergeRecursive(obj1, obj2) {
for (var p in obj2) {
try {
if(obj2[p].constructor == Object) {
obj1[p] = mergeRecursive(obj1[p], obj2[p]);
}
// Property in destination object set; update its value.
else if (Ext.isArray(obj2[p])) {
// obj1[p] = [];
if (obj2[p].length < 1) {
obj1[p] = obj2[p];
}
else {
obj1[p] = mergeRecursive(obj1[p], obj2[p]);
}
}else{
obj1[p] = obj2[p];
}
} catch (e) {
// Property in destination object not set; create it and set its value.
obj1[p] = obj2[p];
}
}
return obj1;
}
this will get you a new object that will merge all the changes between the old object and the new object from your form
I just use ramda, for resolve the same problem, i need to know what is changed in new object. So here my design.
const oldState = {id:'170',name:'Ivab',secondName:'Ivanov',weight:45};
const newState = {id:'170',name:'Ivanko',secondName:'Ivanov',age:29};
const keysObj1 = R.keys(newState)
const filterFunc = key => {
const value = R.eqProps(key,oldState,newState)
return {[key]:value}
}
const result = R.map(filterFunc, keysObj1)
result is, name of property and it's status.
[{"id":true}, {"name":false}, {"secondName":true}, {"age":false}]
The more extended and simplified function from sbgoran's answer.
This allow deep scan and find an array's simillarity.
var result = objectDifference({
a:'i am unchanged',
b:'i am deleted',
e: {a: 1,b:false, c: null},
f: [1,{a: 'same',b:[{a:'same'},{d: 'delete'}]}],
g: new Date('2017.11.25'),
h: [1,2,3,4,5]
},
{
a:'i am unchanged',
c:'i am created',
e: {a: '1', b: '', d:'created'},
f: [{a: 'same',b:[{a:'same'},{c: 'create'}]},1],
g: new Date('2017.11.25'),
h: [4,5,6,7,8]
});
console.log(result);
function objectDifference(obj1, obj2){
if((dataType(obj1) !== 'array' && dataType(obj1) !== 'object') || (dataType(obj2) !== 'array' && dataType(obj2) !== 'object')){
var type = '';
if(obj1 === obj2 || (dataType(obj1) === 'date' && dataType(obj2) === 'date' && obj1.getTime() === obj2.getTime()))
type = 'unchanged';
else if(dataType(obj1) === 'undefined')
type = 'created';
if(dataType(obj2) === 'undefined')
type = 'deleted';
else if(type === '') type = 'updated';
return {
type: type,
data:(obj1 === undefined) ? obj2 : obj1
};
}
if(dataType(obj1) === 'array' && dataType(obj2) === 'array'){
var diff = [];
obj1.sort(); obj2.sort();
for(var i = 0; i < obj2.length; i++){
var type = obj1.indexOf(obj2[i]) === -1?'created':'unchanged';
if(type === 'created' && (dataType(obj2[i]) === 'array' || dataType(obj2[i]) === 'object')){
diff.push(
objectDifference(obj1[i], obj2[i])
);
continue;
}
diff.push({
type: type,
data: obj2[i]
});
}
for(var i = 0; i < obj1.length; i++){
if(obj2.indexOf(obj1[i]) !== -1 || dataType(obj1[i]) === 'array' || dataType(obj1[i]) === 'object')
continue;
diff.push({
type: 'deleted',
data: obj1[i]
});
}
} else {
var diff = {};
var key = Object.keys(obj1);
for(var i = 0; i < key.length; i++){
var value2 = undefined;
if(dataType(obj2[key[i]]) !== 'undefined')
value2 = obj2[key[i]];
diff[key[i]] = objectDifference(obj1[key[i]], value2);
}
var key = Object.keys(obj2);
for(var i = 0; i < key.length; i++){
if(dataType(diff[key[i]]) !== 'undefined')
continue;
diff[key[i]] = objectDifference(undefined, obj2[key[i]]);
}
}
return diff;
}
function dataType(data){
if(data === undefined || data === null) return 'undefined';
if(data.constructor === String) return 'string';
if(data.constructor === Array) return 'array';
if(data.constructor === Object) return 'object';
if(data.constructor === Number) return 'number';
if(data.constructor === Boolean) return 'boolean';
if(data.constructor === Function) return 'function';
if(data.constructor === Date) return 'date';
if(data.constructor === RegExp) return 'regex';
return 'unknown';
}
update 2022:
I came up with a breeze dead-simple algorithm that addresses the most edge cases:
flatten the objects
simple compare the two flattened objects and create a flattened diff object
unflatten the diff object
If you saved the flatted object you can repeat using it and do the "3)unflatten ..." just when you really need
let oldObject = {var1:'value1', var2:{ var1:'value1', var2:'value2'},var3:'value3'};
let newObject = {var2:{ var1:'value11', var3:'value3'},var3:'value3'};
let flatOldObject = flattenObject(oldObject)
/*
{
'var1':'value1',
'var2.var1':'value1',
'var2.var2':'value2',
'var3':'value3'
}
*/
let flatNewObject = flattenObject(newObject)
/*
{
'var2.var1':'value11',
'var2.var3':'value3',
'var3':'value3'
}
*/
let flatDiff = diffFlatten(flatOldObject, flatNewObject)
let [updated,removed] = flatDiff
/*
updated = {
'var2.var1':'value11',
'var2.var3':'value3'
}
removed = {
'var1':'value1'
}
*/
Of course you can come with your implementations for that steps. but here is mine:
Implementations
function flattenObject(obj) {
const object = Object.create(null);
const path = [];
const isObject = (value) => Object(value) === value;
function dig(obj) {
for (let [key, value] of Object.entries(obj)) {
path.push(key);
if (isObject(value)) dig(value);
else object[path.join('.')] = value;
path.pop();
}
}
dig(obj);
return object;
}
function diffFlatten(oldFlat, newFlat) {
const updated = Object.assign({}, oldFlat);
const removed = Object.assign({}, newFlat);
/**delete the unUpdated keys*/
for (let key in newFlat) {
if (newFlat[key] === oldFlat[key]) {
delete updated[key];
delete removed[key];
}
}
return [updated, removed];
}
function unflatenObject(flattenObject) {
const unFlatten = Object.create(null);
for (let [stringKeys, value] of Object.entries(flattenObject)) {
let chain = stringKeys.split('.')
let object = unFlatten
for (let [i, key] of chain.slice(0, -1).entries()) {
if (!object[key]) {
let needArray = Number.isInteger(Number(chain[+i + 1]))
object[key] = needArray ? [] : Object.create(null)
}
object = object[key];
}
let lastkey = chain.pop();
object[lastkey] = value;
}
return unFlatten;
}
I already wrote a function for one of my projects that will comparing an object as a user options with its internal clone.
It also can validate and even replace by default values if the user entered bad type of data or removed, in pure javascript.
In IE8 100% works. Tested successfully.
// ObjectKey: ["DataType, DefaultValue"]
reference = {
a : ["string", 'Defaul value for "a"'],
b : ["number", 300],
c : ["boolean", true],
d : {
da : ["boolean", true],
db : ["string", 'Defaul value for "db"'],
dc : {
dca : ["number", 200],
dcb : ["string", 'Default value for "dcb"'],
dcc : ["number", 500],
dcd : ["boolean", true]
},
dce : ["string", 'Default value for "dce"'],
},
e : ["number", 200],
f : ["boolean", 0],
g : ["", 'This is an internal extra parameter']
};
userOptions = {
a : 999, //Only string allowed
//b : ["number", 400], //User missed this parameter
c: "Hi", //Only lower case or case insitive in quotes true/false allowed.
d : {
da : false,
db : "HelloWorld",
dc : {
dca : 10,
dcb : "My String", //Space is not allowed for ID attr
dcc: "3thString", //Should not start with numbers
dcd : false
},
dce: "ANOTHER STRING",
},
e: 40,
f: true,
};
function compare(ref, obj) {
var validation = {
number: function (defaultValue, userValue) {
if(/^[0-9]+$/.test(userValue))
return userValue;
else return defaultValue;
},
string: function (defaultValue, userValue) {
if(/^[a-z][a-z0-9-_.:]{1,51}[^-_.:]$/i.test(userValue)) //This Regex is validating HTML tag "ID" attributes
return userValue;
else return defaultValue;
},
boolean: function (defaultValue, userValue) {
if (typeof userValue === 'boolean')
return userValue;
else return defaultValue;
}
};
for (var key in ref)
if (obj[key] && obj[key].constructor && obj[key].constructor === Object)
ref[key] = compare(ref[key], obj[key]);
else if(obj.hasOwnProperty(key))
ref[key] = validation[ref[key][0]](ref[key][1], obj[key]); //or without validation on user enties => ref[key] = obj[key]
else ref[key] = ref[key][1];
return ref;
}
//console.log(
alert(JSON.stringify( compare(reference, userOptions),null,2 ))
//);
/* result
{
"a": "Defaul value for \"a\"",
"b": 300,
"c": true,
"d": {
"da": false,
"db": "Defaul value for \"db\"",
"dc": {
"dca": 10,
"dcb": "Default value for \"dcb\"",
"dcc": 500,
"dcd": false
},
"dce": "Default value for \"dce\""
},
"e": 40,
"f": true,
"g": "This is an internal extra parameter"
}
*/
I stumbled here trying to look for a way to get the difference between two objects. This is my solution using Lodash:
// Get updated values (including new values)
var updatedValuesIncl = _.omitBy(curr, (value, key) => _.isEqual(last[key], value));
// Get updated values (excluding new values)
var updatedValuesExcl = _.omitBy(curr, (value, key) => (!_.has(last, key) || _.isEqual(last[key], value)));
// Get old values (by using updated values)
var oldValues = Object.keys(updatedValuesIncl).reduce((acc, key) => { acc[key] = last[key]; return acc; }, {});
// Get newly added values
var newCreatedValues = _.omitBy(curr, (value, key) => _.has(last, key));
// Get removed values
var deletedValues = _.omitBy(last, (value, key) => _.has(curr, key));
// Then you can group them however you want with the result
Code snippet below:
var last = {
"authed": true,
"inForeground": true,
"goodConnection": false,
"inExecutionMode": false,
"online": true,
"array": [1, 2, 3],
"deep": {
"nested": "value",
},
"removed": "value",
};
var curr = {
"authed": true,
"inForeground": true,
"deep": {
"nested": "changed",
},
"array": [1, 2, 4],
"goodConnection": true,
"inExecutionMode": false,
"online": false,
"new": "value"
};
// Get updated values (including new values)
var updatedValuesIncl = _.omitBy(curr, (value, key) => _.isEqual(last[key], value));
// Get updated values (excluding new values)
var updatedValuesExcl = _.omitBy(curr, (value, key) => (!_.has(last, key) || _.isEqual(last[key], value)));
// Get old values (by using updated values)
var oldValues = Object.keys(updatedValuesIncl).reduce((acc, key) => { acc[key] = last[key]; return acc; }, {});
// Get newly added values
var newCreatedValues = _.omitBy(curr, (value, key) => _.has(last, key));
// Get removed values
var deletedValues = _.omitBy(last, (value, key) => _.has(curr, key));
console.log('oldValues', JSON.stringify(oldValues));
console.log('updatedValuesIncl', JSON.stringify(updatedValuesIncl));
console.log('updatedValuesExcl', JSON.stringify(updatedValuesExcl));
console.log('newCreatedValues', JSON.stringify(newCreatedValues));
console.log('deletedValues', JSON.stringify(deletedValues));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>
I took the answer above by #sbgoran and modified it for my case same as the question needed, to treat arrays as sets (i.e. order is not important for diff)
const deepDiffMapper = function () {
return {
VALUE_CREATED: "created",
VALUE_UPDATED: "updated",
VALUE_DELETED: "deleted",
VALUE_UNCHANGED: "unchanged",
map: function(obj1: any, obj2: any) {
if (this.isFunction(obj1) || this.isFunction(obj2)) {
throw "Invalid argument. Function given, object expected.";
}
if (this.isValue(obj1) || this.isValue(obj2)) {
return {
type: this.compareValues(obj1, obj2),
data: obj2 === undefined ? obj1 : obj2
};
}
if (this.isArray(obj1) || this.isArray(obj2)) {
return {
type: this.compareArrays(obj1, obj2),
data: this.getArrayDiffData(obj1, obj2)
};
}
const diff: any = {};
for (const key in obj1) {
if (this.isFunction(obj1[key])) {
continue;
}
let value2 = undefined;
if (obj2[key] !== undefined) {
value2 = obj2[key];
}
diff[key] = this.map(obj1[key], value2);
}
for (const key in obj2) {
if (this.isFunction(obj2[key]) || diff[key] !== undefined) {
continue;
}
diff[key] = this.map(undefined, obj2[key]);
}
return diff;
},
getArrayDiffData: function(arr1: Array<any>, arr2: Array<any>) {
const set1 = new Set(arr1);
const set2 = new Set(arr2);
if (arr1 === undefined || arr2 === undefined) {
return arr1 === undefined ? arr1 : arr2;
}
const deleted = [...arr1].filter(x => !set2.has(x));
const added = [...arr2].filter(x => !set1.has(x));
return {
added, deleted
};
},
compareArrays: function(arr1: Array<any>, arr2: Array<any>) {
const set1 = new Set(arr1);
const set2 = new Set(arr2);
if (_.isEqual(_.sortBy(arr1), _.sortBy(arr2))) {
return this.VALUE_UNCHANGED;
}
if (arr1 === undefined) {
return this.VALUE_CREATED;
}
if (arr2 === undefined) {
return this.VALUE_DELETED;
}
return this.VALUE_UPDATED;
},
compareValues: function (value1: any, value2: any) {
if (value1 === value2) {
return this.VALUE_UNCHANGED;
}
if (this.isDate(value1) && this.isDate(value2) && value1.getTime() === value2.getTime()) {
return this.VALUE_UNCHANGED;
}
if (value1 === undefined) {
return this.VALUE_CREATED;
}
if (value2 === undefined) {
return this.VALUE_DELETED;
}
return this.VALUE_UPDATED;
},
isFunction: function (x: any) {
return Object.prototype.toString.call(x) === "[object Function]";
},
isArray: function (x: any) {
return Object.prototype.toString.call(x) === "[object Array]";
},
isDate: function (x: any) {
return Object.prototype.toString.call(x) === "[object Date]";
},
isObject: function (x: any) {
return Object.prototype.toString.call(x) === "[object Object]";
},
isValue: function (x: any) {
return !this.isObject(x) && !this.isArray(x);
}
};
}();
This returns new object only with changed properties. (omit and isEmpty are functions from lodash)
export const getObjectDifference = <T extends {}>(originalObject: T, newObject: T) => {
const sameProperties: string[] = [];
Object.entries(originalObject).forEach(original => {
Object.entries(newObject).forEach(newObj => {
if (original[0] === newObj[0]) {
if (original[1] === newObj[1])
sameProperties.push(newObj[0]);
}
});
});
const objectDifference: T = omit(newObject, sameProperties) as T;
if (isEmpty(objectDifference))
return null;
else
return objectDifference; }
this will treat [1,2,3] and [3,2,1] as equal (deep object)
since I needed to visualize the difference between:
[
{
"a":1,
"b":1
},
{
"a":1,
"b":1
}
]
and
[
{
"a":1,
"b":1
},
{
"a":"OH NO",
"b":"an insertion"
},
{
"a":1,
"b":1
}
]
so I wanted to see them collide, here's what's left:
[]
and
[
{
"a":"OH NO",
"b":"an insertion"
}
]
imo this is the best way to represent it.
{add:{...},upd:{...},del:{...}} is hard to read
I provide 2 functions : ObjectCollide(obj1,obj2) and ArrayCollide(arr1,arr2)
console.log(ArrayCollide([1,2,3],[3,2,1]))
// false
//everything collided -> false
console.log(ArrayCollide([1],[2,1]))
// [ [], [ 2 ] ]
//1 and 1 collided, even if they are on different indices
//array of objects
const arr1 =
[
{
"a":1,
"b":1
},
{
"a":1,
"b":1
}
]
const arr2 =
[
{
"a":1,
"b":1
},
{
"a":"OH NO",
"b":"an insertion"
},
{
"a":1,
"b":1
}
]
const newArrays = ArrayCollide(arr1, arr2)
console.log(newArrays[0])
console.log(newArrays[1])
console.log('\n')
// []
// [ { a: 'OH NO', b: 'an insertion' } ]
// everything collided until this is left
//ObjectCollide
const obj1 = { a: '111', c: { q: 'no', a: '333' } }
const obj2 = { a: '111', p: 'ok', c: { a: '333' } }
ObjectCollide(obj1, obj2) //in place
console.log(obj1)
console.log(obj2)
console.log('\n')
// { c: { q: 'no' } }
// { p: 'ok', c: {} }
// obj["a"] collided and obj["c"]["a"] collided
//testing empty array
const a1 = { a: [] }
const a2 = { a: [], b: '2' }
ObjectCollide(a1, a2) //in place
console.log(a1)
console.log(a2)
console.log('\n')
// {}
// { b: '2' }
// obj["a"] collided
//DIFFERENT TYPES
const b1 = {a:true}
const b2 = {a:[1,2]}
ObjectCollide(b1,b2) //in place
console.log(b1)
console.log(b2)
// { a: true }
// { a: [ 1, 2 ] }
function ObjectCollide(obj1, obj2) {
//in place, returns true if same
// delete same
const keys = Object.keys(obj1)
const len = keys.length
let howManyDeleted = 0
for (let i = 0; i < len; i++) {
const key = keys[i]
const type1 = Array.isArray(obj1[key]) === true ? 'array' : typeof obj1[key]
const type2 = Array.isArray(obj2[key]) === true ? 'array' : typeof obj2[key]
if (type1!==type2) {
continue
}
switch (type1) {
case 'object':
if (ObjectCollide(obj1[key], obj2[key])) {
delete obj1[key]
delete obj2[key]
howManyDeleted++
}
continue
case 'array':
const newArrays = ArrayCollide(obj1[key], obj2[key])
if (newArrays) {
obj1[key] = newArrays[0]
obj2[key] = newArrays[1]
} else {
delete obj1[key]
delete obj2[key]
howManyDeleted++
}
continue
default:
//string, number, I hope it covers everything else
if (obj1[key] === obj2[key]) {
delete obj1[key]
delete obj2[key]
howManyDeleted++
}
}
}
if (howManyDeleted === len && Object.keys(obj2).length === 0) {
// return 'delete the stuff'
// same. we've deleted everything!
return true
}
}
function ArrayCollide(arr1, arr2) {
// returns [newArr1, newArr2] or false if same arrays (ignore order)
const stringifyObj = {}
const newArr1 = []
const newArr2 = []
for (let i = 0, len = arr1.length; i < len; i++) {
const value = arr1[i]
const stringified = JSON.stringify(value)
stringifyObj[stringified]
// arr = [count, ...]
const arr = stringifyObj[stringified] || (stringifyObj[stringified] = [0])
arr[0]++
arr.push(value)
}
//in 2 but not in 1
for (let i = 0, len = arr2.length; i < len; i++) {
const value = arr2[i]
const stringified = JSON.stringify(value)
const arr = stringifyObj[stringified]
if (arr === undefined) {
newArr2.push(value)
} else {
if (arr[0] === 0) {
newArr2.push(value)
} else {
arr[0]--
}
}
}
//in 1 but not in 2
stringifyKeys = Object.keys(stringifyObj)
for (let i = 0, len = stringifyKeys.length; i < len; i++) {
const arr = stringifyObj[stringifyKeys[i]]
for (let i = 1, len = arr[0] + 1; i < len; i++) {
newArr1.push(arr[i])
}
}
if (newArr1.length || newArr2.length) {
return [newArr1, newArr2]
} else {
return false
}
}
what I was trying to solve:
JSON file keeps reordering, I want to revert the JSON if it's equivalent: like {a:1,b:2} and {b:2,a:1}
but because I don't trust my code (I made a mistake once), I want to see the diff and check it myself, I can Ctrl+F into the original file using this diff.
Below method will create a new object with only changed fields
const findDiff = (obj1, obj2) => {
const isNativeType1 = typeof obj1 !== "object";
const isNativeType2 = typeof obj2 !== "object";
if (isNativeType1 && isNativeType2) {
return obj1 === obj2 ? null : obj2;
}
if (isNativeType1 && !isNativeType2) {
return obj2;
}
if (!isNativeType1 && isNativeType2) {
return obj2;
}
const isArray1 = Array.isArray(obj1);
const isArray2 = Array.isArray(obj2);
if (isArray1 && isArray2) {
const firstLenght = obj1.length;
const secondLenght = obj2.length;
const hasSameLength = firstLenght === secondLenght;
if (!hasSameLength) return obj2;
let hasChange = false;
for (let index = 0; index < obj1.length; index += 1) {
const element1 = obj1[index];
const element2 = obj2[index];
const changed = findDiff(element1, element2);
if (changed) {
hasChange = true;
}
}
return hasChange ? obj2 : null;
}
if (isArray1 || isArray2) return obj2;
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
const hasSameKeys = keys1.length === keys2.length;
if (!hasSameKeys) {
const retObj = { ...obj2 };
for (let index = 0; index < keys1.length; index += 1) {
const key = keys1[index];
if (!keys2.includes(key)) {
retObj[key] = null;
// eslint-disable-next-line no-continue
continue;
}
delete retObj[key];
}
return retObj;
}
let hasChange = false;
const retObj = {};
for (let index = 0; index < keys1.length; index += 1) {
const key = keys1[index];
const element1 = obj1[key];
const element2 = obj2[key];
const changed = findDiff(element1, element2);
if (changed) {
hasChange = true;
}
if (changed) {
retObj[key] = changed;
}
}
return hasChange ? retObj : null;
};
console.log(
JSON.stringify(findDiff(
{
a: 1,
b: 2,
c: {
a: ['1', 'b', { a: 'b', c: false }, true],
},
},
{
a: 1,
b: 2,
c: {
a: ['1','b', { a: 'b', c: true }, true],
},
}
), null, 2)
);
var base = [
{"value": "01", "label": "Pendências"},
{"value": "02", "label": "Ambulatório"},
{"value": "03", "label": "Urgência"},
{"value": "04", "label": "Clínica Médica"},
{"value": "05", "label": "Revisão"},
{"value": "06", "label": "Imagens"},
];
var used = [
{"value": "01", "label": "Pendências"},
{"value": "02", "label": "Ambulatório"},
{"value": "03", "label": "Urgência"},
{"value": "04", "label": "Clínica Médica"},
];
function diff(obj1,obj2) {
var temp = JSON.stringify(obj2.map((x)=> x.value));
return obj1.filter((y)=> temp.indexOf(y.value)<0 && y);
}
var result = diff(base, used);
console.clear();
console.log('RESULTADO');
console.log(result);
codeped
function Difference (ob1,ob2){
let ob3={}
let status=false
for ( var a1 in ob1 ) {
for (var a2 in ob2){
if (a1===a2 && ob1[a1]===ob2[a2]){
status=true;
break;
};
};
if (status===false){
if (ob1[a2]===undefined){
ob3[a1]="ob1:"+ob1[a1]+", ob2:"+ob2[a1];
};
if ( ob2[a1]===undefined){
ob3[a2]="ob1:"+ob1[a2]+", ob2:"+ob2[a2];
}else {
ob3[a1]="ob1:"+ob1[a1] +", ob2:"+ob2[a1];
};
}else {
status=false;
};
};
console.log(ob3);
};
For a simple object diff I like the very straightforward:
function simpleObjectDiff(obj1, obj2) {
return Object.fromEntries(
Object.entries(obj1)
.filter(([key, value]) => value !== obj2[key])
)
}
Related
How to compare and combine two objects [duplicate]
I have two objects: oldObj and newObj. The data in oldObj was used to populate a form and newObj is the result of the user changing data in this form and submitting it. Both objects are deep, ie. they have properties that are objects or arrays of objects etc - they can be n levels deep, thus the diff algorithm needs to be recursive. Now I need to not just figure out what was changed (as in added/updated/deleted) from oldObj to newObj, but also how to best represent it. So far my thoughts was to just build a genericDeepDiffBetweenObjects method that would return an object on the form {add:{...},upd:{...},del:{...}} but then I thought: somebody else must have needed this before. So... does anyone know of a library or a piece of code that will do this and maybe have an even better way of representing the difference (in a way that is still JSON serializable)? Update: I have thought of a better way to represent the updated data, by using the same object structure as newObj, but turning all property values into objects on the form: {type: '<update|create|delete>', data: <propertyValue>} So if newObj.prop1 = 'new value' and oldObj.prop1 = 'old value' it would set returnObj.prop1 = {type: 'update', data: 'new value'} Update 2: It gets truely hairy when we get to properties that are arrays, since the array [1,2,3] should be counted as equal to [2,3,1], which is simple enough for arrays of value based types like string, int & bool, but gets really difficult to handle when it comes to arrays of reference types like objects and arrays. Example arrays that should be found equal: [1,[{c: 1},2,3],{a:'hey'}] and [{a:'hey'},1,[3,{c: 1},2]] Not only is it quite complex to check for this type of deep value equality, but also to figure out a good way to represent the changes that might be.
I wrote a little class that is doing what you want, you can test it here. Only thing that is different from your proposal is that I don't consider [1,[{c: 1},2,3],{a:'hey'}] and [{a:'hey'},1,[3,{c: 1},2]] to be same, because I think that arrays are not equal if order of their elements is not same. Of course this can be changed if needed. Also this code can be further enhanced to take function as argument that will be used to format diff object in arbitrary way based on passed primitive values (now this job is done by "compareValues" method). var deepDiffMapper = function () { return { VALUE_CREATED: 'created', VALUE_UPDATED: 'updated', VALUE_DELETED: 'deleted', VALUE_UNCHANGED: 'unchanged', map: function(obj1, obj2) { if (this.isFunction(obj1) || this.isFunction(obj2)) { throw 'Invalid argument. Function given, object expected.'; } if (this.isValue(obj1) || this.isValue(obj2)) { return { type: this.compareValues(obj1, obj2), data: obj1 === undefined ? obj2 : obj1 }; } var diff = {}; for (var key in obj1) { if (this.isFunction(obj1[key])) { continue; } var value2 = undefined; if (obj2[key] !== undefined) { value2 = obj2[key]; } diff[key] = this.map(obj1[key], value2); } for (var key in obj2) { if (this.isFunction(obj2[key]) || diff[key] !== undefined) { continue; } diff[key] = this.map(undefined, obj2[key]); } return diff; }, compareValues: function (value1, value2) { if (value1 === value2) { return this.VALUE_UNCHANGED; } if (this.isDate(value1) && this.isDate(value2) && value1.getTime() === value2.getTime()) { return this.VALUE_UNCHANGED; } if (value1 === undefined) { return this.VALUE_CREATED; } if (value2 === undefined) { return this.VALUE_DELETED; } return this.VALUE_UPDATED; }, isFunction: function (x) { return Object.prototype.toString.call(x) === '[object Function]'; }, isArray: function (x) { return Object.prototype.toString.call(x) === '[object Array]'; }, isDate: function (x) { return Object.prototype.toString.call(x) === '[object Date]'; }, isObject: function (x) { return Object.prototype.toString.call(x) === '[object Object]'; }, isValue: function (x) { return !this.isObject(x) && !this.isArray(x); } } }(); var result = deepDiffMapper.map({ a: 'i am unchanged', b: 'i am deleted', e: { a: 1, b: false, c: null }, f: [1, { a: 'same', b: [{ a: 'same' }, { d: 'delete' }] }], g: new Date('2017.11.25') }, { a: 'i am unchanged', c: 'i am created', e: { a: '1', b: '', d: 'created' }, f: [{ a: 'same', b: [{ a: 'same' }, { c: 'create' }] }, 1], g: new Date('2017.11.25') }); console.log(result);
Using Underscore, a simple diff: var o1 = {a: 1, b: 2, c: 2}, o2 = {a: 2, b: 1, c: 2}; _.omit(o1, function(v,k) { return o2[k] === v; }) Results in the parts of o1 that correspond but with different values in o2: {a: 1, b: 2} It'd be different for a deep diff: function diff(a,b) { var r = {}; _.each(a, function(v,k) { if(b[k] === v) return; // but what if it returns an empty object? still attach? r[k] = _.isObject(v) ? _.diff(v, b[k]) : v ; }); return r; } As pointed out by #Juhana in the comments, the above is only a diff a-->b and not reversible (meaning extra properties in b would be ignored). Use instead a-->b-->a: (function(_) { function deepDiff(a, b, r) { _.each(a, function(v, k) { // already checked this or equal... if (r.hasOwnProperty(k) || b[k] === v) return; // but what if it returns an empty object? still attach? r[k] = _.isObject(v) ? _.diff(v, b[k]) : v; }); } /* the function */ _.mixin({ diff: function(a, b) { var r = {}; deepDiff(a, b, r); deepDiff(b, a, r); return r; } }); })(_.noConflict()); See http://jsfiddle.net/drzaus/9g5qoxwj/ for full example+tests+mixins
I'd like to offer an ES6 solution...This is a one-way diff, meaning that it will return keys/values from o2 that are not identical to their counterparts in o1: let o1 = { one: 1, two: 2, three: 3 } let o2 = { two: 2, three: 3, four: 4 } let diff = Object.keys(o2).reduce((diff, key) => { if (o1[key] === o2[key]) return diff return { ...diff, [key]: o2[key] } }, {})
Here is a JavaScript library which can be used for finding diff between two JavaScript objects: Github URL: https://github.com/cosmicanant/recursive-diff Npmjs url: https://www.npmjs.com/package/recursive-diff recursive-diff library can be used in the browser as well as in a Node.js based server side application. For browser, it can be used as below: <script type="text" src="https://unpkg.com/recursive-diff#latest/dist/recursive-diff.min.js"/> <script type="text/javascript"> const ob1 = {a:1, b: [2,3]}; const ob2 = {a:2, b: [3,3,1]}; const delta = recursiveDiff.getDiff(ob1,ob2); /* console.log(delta) will dump following data [ {path: ['a'], op: 'update', val: 2} {path: ['b', '0'], op: 'update',val: 3}, {path: ['b',2], op: 'add', val: 1 }, ] */ const ob3 = recursiveDiff.applyDiff(ob1, delta); //expect ob3 is deep equal to ob2 </script> Whereas in a node.js based application it can be used as below: const diff = require('recursive-diff'); const ob1 = {a: 1}, ob2: {b:2}; const diff = diff.getDiff(ob1, ob2);
Using Lodash: _.mergeWith(oldObj, newObj, function (objectValue, sourceValue, key, object, source) { if ( !(_.isEqual(objectValue, sourceValue)) && (Object(objectValue) !== objectValue)) { console.log(key + "\n Expected: " + sourceValue + "\n Actual: " + objectValue); } }); I don't use key/object/source but I left it in there if you need to access them. The object comparison just prevents the console from printing the differences to the console from the outermost element to the innermost element. You can add some logic inside to handle arrays. Perhaps sort the arrays first. This is a very flexible solution. EDIT Changed from _.merge to _.mergeWith due to lodash update. Thanks Aviron for noticing the change.
const diff = require("deep-object-diff").diff; let differences = diff(obj2, obj1); There is an npm module with over 500k weekly downloads: https://www.npmjs.com/package/deep-object-diff I like the object like representation of the differences - especially it is easy to see the structure, when it is formated. const diff = require("deep-object-diff").diff; const lhs = { foo: { bar: { a: ['a', 'b'], b: 2, c: ['x', 'y'], e: 100 // deleted } }, buzz: 'world' }; const rhs = { foo: { bar: { a: ['a'], // index 1 ('b') deleted b: 2, // unchanged c: ['x', 'y', 'z'], // 'z' added d: 'Hello, world!' // added } }, buzz: 'fizz' // updated }; console.log(diff(lhs, rhs)); // => /* { foo: { bar: { a: { '1': undefined }, c: { '2': 'z' }, d: 'Hello, world!', e: undefined } }, buzz: 'fizz' } */
Here is a solution that is: TypeScript (but easily convertible to JavaScript) have no lib dependencies generic, and doesn't care about checking object types (aside the object type) supports properties with value undefined deep of not (default) First we define the comparison result interface: export interface ObjectDiff { added: {} | ObjectDiff; updated: { [propName: string]: Update | ObjectDiff; }; removed: {} | ObjectDiff; unchanged: {} | ObjectDiff; } with the special case of change where we want to know what are old and new values: export interface Update { oldValue: any; newValue: any; } Then we can provide the diff function which is merely two loops (with recursivity if deep is true): export class ObjectUtils { /** * #return if obj is an Object, including an Array. */ static isObject(obj: any) { return obj !== null && typeof obj === 'object'; } /** * #param oldObj The previous Object or Array. * #param newObj The new Object or Array. * #param deep If the comparison must be performed deeper than 1st-level properties. * #return A difference summary between the two objects. */ static diff(oldObj: {}, newObj: {}, deep = false): ObjectDiff { const added = {}; const updated = {}; const removed = {}; const unchanged = {}; for (const oldProp in oldObj) { if (oldObj.hasOwnProperty(oldProp)) { const newPropValue = newObj[oldProp]; const oldPropValue = oldObj[oldProp]; if (newObj.hasOwnProperty(oldProp)) { if (newPropValue === oldPropValue) { unchanged[oldProp] = oldPropValue; } else { updated[oldProp] = deep && this.isObject(oldPropValue) && this.isObject(newPropValue) ? this.diff(oldPropValue, newPropValue, deep) : {newValue: newPropValue}; } } else { removed[oldProp] = oldPropValue; } } } for (const newProp in newObj) { if (newObj.hasOwnProperty(newProp)) { const oldPropValue = oldObj[newProp]; const newPropValue = newObj[newProp]; if (oldObj.hasOwnProperty(newProp)) { if (oldPropValue !== newPropValue) { if (!deep || !this.isObject(oldPropValue)) { updated[newProp].oldValue = oldPropValue; } } } else { added[newProp] = newPropValue; } } } return {added, updated, removed, unchanged}; } } As an example, calling: ObjectUtils.diff( { a: 'a', b: 'b', c: 'c', arr: ['A', 'B'], obj: {p1: 'p1', p2: 'p2'} }, { b: 'x', c: 'c', arr: ['B', 'C'], obj: {p2: 'p2', p3: 'p3'}, d: 'd' }, ); will return: { added: {d: 'd'}, updated: { b: {oldValue: 'b', newValue: 'x'}, arr: {oldValue: ['A', 'B'], newValue: ['B', 'C']}, obj: {oldValue: {p1: 'p1', p2: 'p2'}, newValue: {p2: 'p2', p3: 'p3'}} }, removed: {a: 'a'}, unchanged: {c: 'c'}, } and calling the same with the deep third parameter will return: { added: {d: 'd'}, updated: { b: {oldValue: 'b', newValue: 'x'}, arr: { added: {}, removed: {}, unchanged: {}, updated: { 0: {oldValue: 'A', newValue: 'B'}, 1: {oldValue: 'B', newValue: 'C', } } }, obj: { added: {p3: 'p3'}, removed: {p1: 'p1'}, unchanged: {p2: 'p2'}, updated: {} } }, removed: {a: 'a'}, unchanged: {c: 'c'}, }
I modified #sbgoran's answer so that the resulting diff object includes only the changed values, and omits values that were the same. In addition, it shows both the original value and the updated value. var deepDiffMapper = function () { return { VALUE_CREATED: 'created', VALUE_UPDATED: 'updated', VALUE_DELETED: 'deleted', VALUE_UNCHANGED: '---', map: function (obj1, obj2) { if (this.isFunction(obj1) || this.isFunction(obj2)) { throw 'Invalid argument. Function given, object expected.'; } if (this.isValue(obj1) || this.isValue(obj2)) { let returnObj = { type: this.compareValues(obj1, obj2), original: obj1, updated: obj2, }; if (returnObj.type != this.VALUE_UNCHANGED) { return returnObj; } return undefined; } var diff = {}; let foundKeys = {}; for (var key in obj1) { if (this.isFunction(obj1[key])) { continue; } var value2 = undefined; if (obj2[key] !== undefined) { value2 = obj2[key]; } let mapValue = this.map(obj1[key], value2); foundKeys[key] = true; if (mapValue) { diff[key] = mapValue; } } for (var key in obj2) { if (this.isFunction(obj2[key]) || foundKeys[key] !== undefined) { continue; } let mapValue = this.map(undefined, obj2[key]); if (mapValue) { diff[key] = mapValue; } } //2020-06-13: object length code copied from https://stackoverflow.com/a/13190981/2336212 if (Object.keys(diff).length > 0) { return diff; } return undefined; }, compareValues: function (value1, value2) { if (value1 === value2) { return this.VALUE_UNCHANGED; } if (this.isDate(value1) && this.isDate(value2) && value1.getTime() === value2.getTime()) { return this.VALUE_UNCHANGED; } if (value1 === undefined) { return this.VALUE_CREATED; } if (value2 === undefined) { return this.VALUE_DELETED; } return this.VALUE_UPDATED; }, isFunction: function (x) { return Object.prototype.toString.call(x) === '[object Function]'; }, isArray: function (x) { return Object.prototype.toString.call(x) === '[object Array]'; }, isDate: function (x) { return Object.prototype.toString.call(x) === '[object Date]'; }, isObject: function (x) { return Object.prototype.toString.call(x) === '[object Object]'; }, isValue: function (x) { return !this.isObject(x) && !this.isArray(x); } } }();
These days, there are quite a few modules available for this. I recently wrote a module to do this, because I wasn't satisfied with the numerous diffing modules I found. Its called odiff: https://github.com/Tixit/odiff . I also listed a bunch of the most popular modules and why they weren't acceptable in the readme of odiff, which you could take a look through if odiff doesn't have the properties you want. Here's 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}]} ] */
I know I'm late to the party, but I needed something similar that the above answers didn't help. I was using Angular's $watch function to detect changes in a variable. Not only did I need to know whether a property had changed on the variable, but I also wanted to make sure that the property that changed was not a temporary, calculated field. In other words, I wanted to ignore certain properties. Here's the code: function diff(obj1,obj2,exclude) { var r = {}; if (!exclude) exclude = []; for (var prop in obj1) { if (obj1.hasOwnProperty(prop) && prop != '__proto__') { if (exclude.indexOf(obj1[prop]) == -1) { // check if obj2 has prop if (!obj2.hasOwnProperty(prop)) r[prop] = obj1[prop]; // check if prop is object and // NOT a JavaScript engine object (i.e. __proto__), if so, recursive diff else if (obj1[prop] === Object(obj1[prop])) { var difference = diff(obj1[prop], obj2[prop]); if (Object.keys(difference).length > 0) r[prop] = difference; } // check if obj1 and obj2 are equal else if (obj1[prop] !== obj2[prop]) { if (obj1[prop] === undefined) r[prop] = 'undefined'; if (obj1[prop] === null) r[prop] = null; else if (typeof obj1[prop] === 'function') r[prop] = 'function'; else if (typeof obj1[prop] === 'object') r[prop] = 'object'; else r[prop] = obj1[prop]; } } } } return r; } https://jsfiddle.net/rv01x6jo/ Here's how to use it: // To only return the difference var difference = diff(newValue, oldValue); // To exclude certain properties var difference = diff(newValue, oldValue, [newValue.prop1, newValue.prop2, newValue.prop3]); Hope this helps someone.
Another lodash based solution which is a bit specific to the case when we want to see the diff for the update made to an object: const diff = return { old: _.pickBy(oldObject, (value, key) => { return !_.isEqual(value, newObject[key]); }), new: _.pickBy(newObject, (value, key) => { return !_.isEqual(oldObject[key], value); }) } Didn't use _.omitBy because of performance implications.
I've developed the Function named "compareValue()" in Javascript. it returns whether the value is same or not. I've called compareValue() in for loop of one Object. you can get difference of two objects in diffParams. var diffParams = {}; var obj1 = {"a":"1", "b":"2", "c":[{"key":"3"}]}, obj2 = {"a":"1", "b":"66", "c":[{"key":"55"}]}; for( var p in obj1 ){ if ( !compareValue(obj1[p], obj2[p]) ){ diffParams[p] = obj1[p]; } } function compareValue(val1, val2){ var isSame = true; for ( var p in val1 ) { if (typeof(val1[p]) === "object"){ var objectValue1 = val1[p], objectValue2 = val2[p]; for( var value in objectValue1 ){ isSame = compareValue(objectValue1[value], objectValue2[value]); if( isSame === false ){ return false; } } }else{ if(val1 !== val2){ isSame = false; } } } return isSame; } console.log(diffParams);
I composed this for my own use-case (es5 environment), thought this might be useful for someone, so here it is: function deepCompare(obj1, obj2) { var diffObj = Array.isArray(obj2) ? [] : {} Object.getOwnPropertyNames(obj2).forEach(function(prop) { if (typeof obj2[prop] === 'object') { diffObj[prop] = deepCompare(obj1[prop], obj2[prop]) // if it's an array with only length property => empty array => delete // or if it's an object with no own properties => delete if (Array.isArray(diffObj[prop]) && Object.getOwnPropertyNames(diffObj[prop]).length === 1 || Object.getOwnPropertyNames(diffObj[prop]).length === 0) { delete diffObj[prop] } } else if(obj1[prop] !== obj2[prop]) { diffObj[prop] = obj2[prop] } }); return diffObj } This might be not really efficient, but will output an object with only different props based on second Obj.
Here is a typescript version of #sbgoran code export class deepDiffMapper { static VALUE_CREATED = 'created'; static VALUE_UPDATED = 'updated'; static VALUE_DELETED = 'deleted'; static VALUE_UNCHANGED ='unchanged'; protected isFunction(obj: object) { return {}.toString.apply(obj) === '[object Function]'; }; protected isArray(obj: object) { return {}.toString.apply(obj) === '[object Array]'; }; protected isObject(obj: object) { return {}.toString.apply(obj) === '[object Object]'; }; protected isDate(obj: object) { return {}.toString.apply(obj) === '[object Date]'; }; protected isValue(obj: object) { return !this.isObject(obj) && !this.isArray(obj); }; protected compareValues (value1: any, value2: any) { if (value1 === value2) { return deepDiffMapper.VALUE_UNCHANGED; } if (this.isDate(value1) && this.isDate(value2) && value1.getTime() === value2.getTime()) { return deepDiffMapper.VALUE_UNCHANGED; } if ('undefined' == typeof(value1)) { return deepDiffMapper.VALUE_CREATED; } if ('undefined' == typeof(value2)) { return deepDiffMapper.VALUE_DELETED; } return deepDiffMapper.VALUE_UPDATED; } public map(obj1: object, obj2: object) { if (this.isFunction(obj1) || this.isFunction(obj2)) { throw 'Invalid argument. Function given, object expected.'; } if (this.isValue(obj1) || this.isValue(obj2)) { return { type: this.compareValues(obj1, obj2), data: (obj1 === undefined) ? obj2 : obj1 }; } var diff = {}; for (var key in obj1) { if (this.isFunction(obj1[key])) { continue; } var value2 = undefined; if ('undefined' != typeof(obj2[key])) { value2 = obj2[key]; } diff[key] = this.map(obj1[key], value2); } for (var key in obj2) { if (this.isFunction(obj2[key]) || ('undefined' != typeof(diff[key]))) { continue; } diff[key] = this.map(undefined, obj2[key]); } return diff; } }
Here is a modified version of something found on gisthub. isNullBlankOrUndefined = function (o) { return (typeof o === "undefined" || o == null || o === ""); } /** * Deep diff between two object, using lodash * #param {Object} object Object compared * #param {Object} base Object to compare with * #param {Object} ignoreBlanks will not include properties whose value is null, undefined, etc. * #return {Object} Return a new object who represent the diff */ objectDifference = function (object, base, ignoreBlanks = false) { if (!lodash.isObject(object) || lodash.isDate(object)) return object // special case dates return lodash.transform(object, (result, value, key) => { if (!lodash.isEqual(value, base[key])) { if (ignoreBlanks && du.isNullBlankOrUndefined(value) && isNullBlankOrUndefined( base[key])) return; result[key] = lodash.isObject(value) && lodash.isObject(base[key]) ? objectDifference(value, base[key]) : value; } }); }
I have used this piece of code for doing the task that you describe: function mergeRecursive(obj1, obj2) { for (var p in obj2) { try { if(obj2[p].constructor == Object) { obj1[p] = mergeRecursive(obj1[p], obj2[p]); } // Property in destination object set; update its value. else if (Ext.isArray(obj2[p])) { // obj1[p] = []; if (obj2[p].length < 1) { obj1[p] = obj2[p]; } else { obj1[p] = mergeRecursive(obj1[p], obj2[p]); } }else{ obj1[p] = obj2[p]; } } catch (e) { // Property in destination object not set; create it and set its value. obj1[p] = obj2[p]; } } return obj1; } this will get you a new object that will merge all the changes between the old object and the new object from your form
I just use ramda, for resolve the same problem, i need to know what is changed in new object. So here my design. const oldState = {id:'170',name:'Ivab',secondName:'Ivanov',weight:45}; const newState = {id:'170',name:'Ivanko',secondName:'Ivanov',age:29}; const keysObj1 = R.keys(newState) const filterFunc = key => { const value = R.eqProps(key,oldState,newState) return {[key]:value} } const result = R.map(filterFunc, keysObj1) result is, name of property and it's status. [{"id":true}, {"name":false}, {"secondName":true}, {"age":false}]
The more extended and simplified function from sbgoran's answer. This allow deep scan and find an array's simillarity. var result = objectDifference({ a:'i am unchanged', b:'i am deleted', e: {a: 1,b:false, c: null}, f: [1,{a: 'same',b:[{a:'same'},{d: 'delete'}]}], g: new Date('2017.11.25'), h: [1,2,3,4,5] }, { a:'i am unchanged', c:'i am created', e: {a: '1', b: '', d:'created'}, f: [{a: 'same',b:[{a:'same'},{c: 'create'}]},1], g: new Date('2017.11.25'), h: [4,5,6,7,8] }); console.log(result); function objectDifference(obj1, obj2){ if((dataType(obj1) !== 'array' && dataType(obj1) !== 'object') || (dataType(obj2) !== 'array' && dataType(obj2) !== 'object')){ var type = ''; if(obj1 === obj2 || (dataType(obj1) === 'date' && dataType(obj2) === 'date' && obj1.getTime() === obj2.getTime())) type = 'unchanged'; else if(dataType(obj1) === 'undefined') type = 'created'; if(dataType(obj2) === 'undefined') type = 'deleted'; else if(type === '') type = 'updated'; return { type: type, data:(obj1 === undefined) ? obj2 : obj1 }; } if(dataType(obj1) === 'array' && dataType(obj2) === 'array'){ var diff = []; obj1.sort(); obj2.sort(); for(var i = 0; i < obj2.length; i++){ var type = obj1.indexOf(obj2[i]) === -1?'created':'unchanged'; if(type === 'created' && (dataType(obj2[i]) === 'array' || dataType(obj2[i]) === 'object')){ diff.push( objectDifference(obj1[i], obj2[i]) ); continue; } diff.push({ type: type, data: obj2[i] }); } for(var i = 0; i < obj1.length; i++){ if(obj2.indexOf(obj1[i]) !== -1 || dataType(obj1[i]) === 'array' || dataType(obj1[i]) === 'object') continue; diff.push({ type: 'deleted', data: obj1[i] }); } } else { var diff = {}; var key = Object.keys(obj1); for(var i = 0; i < key.length; i++){ var value2 = undefined; if(dataType(obj2[key[i]]) !== 'undefined') value2 = obj2[key[i]]; diff[key[i]] = objectDifference(obj1[key[i]], value2); } var key = Object.keys(obj2); for(var i = 0; i < key.length; i++){ if(dataType(diff[key[i]]) !== 'undefined') continue; diff[key[i]] = objectDifference(undefined, obj2[key[i]]); } } return diff; } function dataType(data){ if(data === undefined || data === null) return 'undefined'; if(data.constructor === String) return 'string'; if(data.constructor === Array) return 'array'; if(data.constructor === Object) return 'object'; if(data.constructor === Number) return 'number'; if(data.constructor === Boolean) return 'boolean'; if(data.constructor === Function) return 'function'; if(data.constructor === Date) return 'date'; if(data.constructor === RegExp) return 'regex'; return 'unknown'; }
update 2022: I came up with a breeze dead-simple algorithm that addresses the most edge cases: flatten the objects simple compare the two flattened objects and create a flattened diff object unflatten the diff object If you saved the flatted object you can repeat using it and do the "3)unflatten ..." just when you really need let oldObject = {var1:'value1', var2:{ var1:'value1', var2:'value2'},var3:'value3'}; let newObject = {var2:{ var1:'value11', var3:'value3'},var3:'value3'}; let flatOldObject = flattenObject(oldObject) /* { 'var1':'value1', 'var2.var1':'value1', 'var2.var2':'value2', 'var3':'value3' } */ let flatNewObject = flattenObject(newObject) /* { 'var2.var1':'value11', 'var2.var3':'value3', 'var3':'value3' } */ let flatDiff = diffFlatten(flatOldObject, flatNewObject) let [updated,removed] = flatDiff /* updated = { 'var2.var1':'value11', 'var2.var3':'value3' } removed = { 'var1':'value1' } */ Of course you can come with your implementations for that steps. but here is mine: Implementations function flattenObject(obj) { const object = Object.create(null); const path = []; const isObject = (value) => Object(value) === value; function dig(obj) { for (let [key, value] of Object.entries(obj)) { path.push(key); if (isObject(value)) dig(value); else object[path.join('.')] = value; path.pop(); } } dig(obj); return object; } function diffFlatten(oldFlat, newFlat) { const updated = Object.assign({}, oldFlat); const removed = Object.assign({}, newFlat); /**delete the unUpdated keys*/ for (let key in newFlat) { if (newFlat[key] === oldFlat[key]) { delete updated[key]; delete removed[key]; } } return [updated, removed]; } function unflatenObject(flattenObject) { const unFlatten = Object.create(null); for (let [stringKeys, value] of Object.entries(flattenObject)) { let chain = stringKeys.split('.') let object = unFlatten for (let [i, key] of chain.slice(0, -1).entries()) { if (!object[key]) { let needArray = Number.isInteger(Number(chain[+i + 1])) object[key] = needArray ? [] : Object.create(null) } object = object[key]; } let lastkey = chain.pop(); object[lastkey] = value; } return unFlatten; }
I already wrote a function for one of my projects that will comparing an object as a user options with its internal clone. It also can validate and even replace by default values if the user entered bad type of data or removed, in pure javascript. In IE8 100% works. Tested successfully. // ObjectKey: ["DataType, DefaultValue"] reference = { a : ["string", 'Defaul value for "a"'], b : ["number", 300], c : ["boolean", true], d : { da : ["boolean", true], db : ["string", 'Defaul value for "db"'], dc : { dca : ["number", 200], dcb : ["string", 'Default value for "dcb"'], dcc : ["number", 500], dcd : ["boolean", true] }, dce : ["string", 'Default value for "dce"'], }, e : ["number", 200], f : ["boolean", 0], g : ["", 'This is an internal extra parameter'] }; userOptions = { a : 999, //Only string allowed //b : ["number", 400], //User missed this parameter c: "Hi", //Only lower case or case insitive in quotes true/false allowed. d : { da : false, db : "HelloWorld", dc : { dca : 10, dcb : "My String", //Space is not allowed for ID attr dcc: "3thString", //Should not start with numbers dcd : false }, dce: "ANOTHER STRING", }, e: 40, f: true, }; function compare(ref, obj) { var validation = { number: function (defaultValue, userValue) { if(/^[0-9]+$/.test(userValue)) return userValue; else return defaultValue; }, string: function (defaultValue, userValue) { if(/^[a-z][a-z0-9-_.:]{1,51}[^-_.:]$/i.test(userValue)) //This Regex is validating HTML tag "ID" attributes return userValue; else return defaultValue; }, boolean: function (defaultValue, userValue) { if (typeof userValue === 'boolean') return userValue; else return defaultValue; } }; for (var key in ref) if (obj[key] && obj[key].constructor && obj[key].constructor === Object) ref[key] = compare(ref[key], obj[key]); else if(obj.hasOwnProperty(key)) ref[key] = validation[ref[key][0]](ref[key][1], obj[key]); //or without validation on user enties => ref[key] = obj[key] else ref[key] = ref[key][1]; return ref; } //console.log( alert(JSON.stringify( compare(reference, userOptions),null,2 )) //); /* result { "a": "Defaul value for \"a\"", "b": 300, "c": true, "d": { "da": false, "db": "Defaul value for \"db\"", "dc": { "dca": 10, "dcb": "Default value for \"dcb\"", "dcc": 500, "dcd": false }, "dce": "Default value for \"dce\"" }, "e": 40, "f": true, "g": "This is an internal extra parameter" } */
I stumbled here trying to look for a way to get the difference between two objects. This is my solution using Lodash: // Get updated values (including new values) var updatedValuesIncl = _.omitBy(curr, (value, key) => _.isEqual(last[key], value)); // Get updated values (excluding new values) var updatedValuesExcl = _.omitBy(curr, (value, key) => (!_.has(last, key) || _.isEqual(last[key], value))); // Get old values (by using updated values) var oldValues = Object.keys(updatedValuesIncl).reduce((acc, key) => { acc[key] = last[key]; return acc; }, {}); // Get newly added values var newCreatedValues = _.omitBy(curr, (value, key) => _.has(last, key)); // Get removed values var deletedValues = _.omitBy(last, (value, key) => _.has(curr, key)); // Then you can group them however you want with the result Code snippet below: var last = { "authed": true, "inForeground": true, "goodConnection": false, "inExecutionMode": false, "online": true, "array": [1, 2, 3], "deep": { "nested": "value", }, "removed": "value", }; var curr = { "authed": true, "inForeground": true, "deep": { "nested": "changed", }, "array": [1, 2, 4], "goodConnection": true, "inExecutionMode": false, "online": false, "new": "value" }; // Get updated values (including new values) var updatedValuesIncl = _.omitBy(curr, (value, key) => _.isEqual(last[key], value)); // Get updated values (excluding new values) var updatedValuesExcl = _.omitBy(curr, (value, key) => (!_.has(last, key) || _.isEqual(last[key], value))); // Get old values (by using updated values) var oldValues = Object.keys(updatedValuesIncl).reduce((acc, key) => { acc[key] = last[key]; return acc; }, {}); // Get newly added values var newCreatedValues = _.omitBy(curr, (value, key) => _.has(last, key)); // Get removed values var deletedValues = _.omitBy(last, (value, key) => _.has(curr, key)); console.log('oldValues', JSON.stringify(oldValues)); console.log('updatedValuesIncl', JSON.stringify(updatedValuesIncl)); console.log('updatedValuesExcl', JSON.stringify(updatedValuesExcl)); console.log('newCreatedValues', JSON.stringify(newCreatedValues)); console.log('deletedValues', JSON.stringify(deletedValues)); <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>
I took the answer above by #sbgoran and modified it for my case same as the question needed, to treat arrays as sets (i.e. order is not important for diff) const deepDiffMapper = function () { return { VALUE_CREATED: "created", VALUE_UPDATED: "updated", VALUE_DELETED: "deleted", VALUE_UNCHANGED: "unchanged", map: function(obj1: any, obj2: any) { if (this.isFunction(obj1) || this.isFunction(obj2)) { throw "Invalid argument. Function given, object expected."; } if (this.isValue(obj1) || this.isValue(obj2)) { return { type: this.compareValues(obj1, obj2), data: obj2 === undefined ? obj1 : obj2 }; } if (this.isArray(obj1) || this.isArray(obj2)) { return { type: this.compareArrays(obj1, obj2), data: this.getArrayDiffData(obj1, obj2) }; } const diff: any = {}; for (const key in obj1) { if (this.isFunction(obj1[key])) { continue; } let value2 = undefined; if (obj2[key] !== undefined) { value2 = obj2[key]; } diff[key] = this.map(obj1[key], value2); } for (const key in obj2) { if (this.isFunction(obj2[key]) || diff[key] !== undefined) { continue; } diff[key] = this.map(undefined, obj2[key]); } return diff; }, getArrayDiffData: function(arr1: Array<any>, arr2: Array<any>) { const set1 = new Set(arr1); const set2 = new Set(arr2); if (arr1 === undefined || arr2 === undefined) { return arr1 === undefined ? arr1 : arr2; } const deleted = [...arr1].filter(x => !set2.has(x)); const added = [...arr2].filter(x => !set1.has(x)); return { added, deleted }; }, compareArrays: function(arr1: Array<any>, arr2: Array<any>) { const set1 = new Set(arr1); const set2 = new Set(arr2); if (_.isEqual(_.sortBy(arr1), _.sortBy(arr2))) { return this.VALUE_UNCHANGED; } if (arr1 === undefined) { return this.VALUE_CREATED; } if (arr2 === undefined) { return this.VALUE_DELETED; } return this.VALUE_UPDATED; }, compareValues: function (value1: any, value2: any) { if (value1 === value2) { return this.VALUE_UNCHANGED; } if (this.isDate(value1) && this.isDate(value2) && value1.getTime() === value2.getTime()) { return this.VALUE_UNCHANGED; } if (value1 === undefined) { return this.VALUE_CREATED; } if (value2 === undefined) { return this.VALUE_DELETED; } return this.VALUE_UPDATED; }, isFunction: function (x: any) { return Object.prototype.toString.call(x) === "[object Function]"; }, isArray: function (x: any) { return Object.prototype.toString.call(x) === "[object Array]"; }, isDate: function (x: any) { return Object.prototype.toString.call(x) === "[object Date]"; }, isObject: function (x: any) { return Object.prototype.toString.call(x) === "[object Object]"; }, isValue: function (x: any) { return !this.isObject(x) && !this.isArray(x); } }; }();
This returns new object only with changed properties. (omit and isEmpty are functions from lodash) export const getObjectDifference = <T extends {}>(originalObject: T, newObject: T) => { const sameProperties: string[] = []; Object.entries(originalObject).forEach(original => { Object.entries(newObject).forEach(newObj => { if (original[0] === newObj[0]) { if (original[1] === newObj[1]) sameProperties.push(newObj[0]); } }); }); const objectDifference: T = omit(newObject, sameProperties) as T; if (isEmpty(objectDifference)) return null; else return objectDifference; }
this will treat [1,2,3] and [3,2,1] as equal (deep object) since I needed to visualize the difference between: [ { "a":1, "b":1 }, { "a":1, "b":1 } ] and [ { "a":1, "b":1 }, { "a":"OH NO", "b":"an insertion" }, { "a":1, "b":1 } ] so I wanted to see them collide, here's what's left: [] and [ { "a":"OH NO", "b":"an insertion" } ] imo this is the best way to represent it. {add:{...},upd:{...},del:{...}} is hard to read I provide 2 functions : ObjectCollide(obj1,obj2) and ArrayCollide(arr1,arr2) console.log(ArrayCollide([1,2,3],[3,2,1])) // false //everything collided -> false console.log(ArrayCollide([1],[2,1])) // [ [], [ 2 ] ] //1 and 1 collided, even if they are on different indices //array of objects const arr1 = [ { "a":1, "b":1 }, { "a":1, "b":1 } ] const arr2 = [ { "a":1, "b":1 }, { "a":"OH NO", "b":"an insertion" }, { "a":1, "b":1 } ] const newArrays = ArrayCollide(arr1, arr2) console.log(newArrays[0]) console.log(newArrays[1]) console.log('\n') // [] // [ { a: 'OH NO', b: 'an insertion' } ] // everything collided until this is left //ObjectCollide const obj1 = { a: '111', c: { q: 'no', a: '333' } } const obj2 = { a: '111', p: 'ok', c: { a: '333' } } ObjectCollide(obj1, obj2) //in place console.log(obj1) console.log(obj2) console.log('\n') // { c: { q: 'no' } } // { p: 'ok', c: {} } // obj["a"] collided and obj["c"]["a"] collided //testing empty array const a1 = { a: [] } const a2 = { a: [], b: '2' } ObjectCollide(a1, a2) //in place console.log(a1) console.log(a2) console.log('\n') // {} // { b: '2' } // obj["a"] collided //DIFFERENT TYPES const b1 = {a:true} const b2 = {a:[1,2]} ObjectCollide(b1,b2) //in place console.log(b1) console.log(b2) // { a: true } // { a: [ 1, 2 ] } function ObjectCollide(obj1, obj2) { //in place, returns true if same // delete same const keys = Object.keys(obj1) const len = keys.length let howManyDeleted = 0 for (let i = 0; i < len; i++) { const key = keys[i] const type1 = Array.isArray(obj1[key]) === true ? 'array' : typeof obj1[key] const type2 = Array.isArray(obj2[key]) === true ? 'array' : typeof obj2[key] if (type1!==type2) { continue } switch (type1) { case 'object': if (ObjectCollide(obj1[key], obj2[key])) { delete obj1[key] delete obj2[key] howManyDeleted++ } continue case 'array': const newArrays = ArrayCollide(obj1[key], obj2[key]) if (newArrays) { obj1[key] = newArrays[0] obj2[key] = newArrays[1] } else { delete obj1[key] delete obj2[key] howManyDeleted++ } continue default: //string, number, I hope it covers everything else if (obj1[key] === obj2[key]) { delete obj1[key] delete obj2[key] howManyDeleted++ } } } if (howManyDeleted === len && Object.keys(obj2).length === 0) { // return 'delete the stuff' // same. we've deleted everything! return true } } function ArrayCollide(arr1, arr2) { // returns [newArr1, newArr2] or false if same arrays (ignore order) const stringifyObj = {} const newArr1 = [] const newArr2 = [] for (let i = 0, len = arr1.length; i < len; i++) { const value = arr1[i] const stringified = JSON.stringify(value) stringifyObj[stringified] // arr = [count, ...] const arr = stringifyObj[stringified] || (stringifyObj[stringified] = [0]) arr[0]++ arr.push(value) } //in 2 but not in 1 for (let i = 0, len = arr2.length; i < len; i++) { const value = arr2[i] const stringified = JSON.stringify(value) const arr = stringifyObj[stringified] if (arr === undefined) { newArr2.push(value) } else { if (arr[0] === 0) { newArr2.push(value) } else { arr[0]-- } } } //in 1 but not in 2 stringifyKeys = Object.keys(stringifyObj) for (let i = 0, len = stringifyKeys.length; i < len; i++) { const arr = stringifyObj[stringifyKeys[i]] for (let i = 1, len = arr[0] + 1; i < len; i++) { newArr1.push(arr[i]) } } if (newArr1.length || newArr2.length) { return [newArr1, newArr2] } else { return false } } what I was trying to solve: JSON file keeps reordering, I want to revert the JSON if it's equivalent: like {a:1,b:2} and {b:2,a:1} but because I don't trust my code (I made a mistake once), I want to see the diff and check it myself, I can Ctrl+F into the original file using this diff.
Below method will create a new object with only changed fields const findDiff = (obj1, obj2) => { const isNativeType1 = typeof obj1 !== "object"; const isNativeType2 = typeof obj2 !== "object"; if (isNativeType1 && isNativeType2) { return obj1 === obj2 ? null : obj2; } if (isNativeType1 && !isNativeType2) { return obj2; } if (!isNativeType1 && isNativeType2) { return obj2; } const isArray1 = Array.isArray(obj1); const isArray2 = Array.isArray(obj2); if (isArray1 && isArray2) { const firstLenght = obj1.length; const secondLenght = obj2.length; const hasSameLength = firstLenght === secondLenght; if (!hasSameLength) return obj2; let hasChange = false; for (let index = 0; index < obj1.length; index += 1) { const element1 = obj1[index]; const element2 = obj2[index]; const changed = findDiff(element1, element2); if (changed) { hasChange = true; } } return hasChange ? obj2 : null; } if (isArray1 || isArray2) return obj2; const keys1 = Object.keys(obj1); const keys2 = Object.keys(obj2); const hasSameKeys = keys1.length === keys2.length; if (!hasSameKeys) { const retObj = { ...obj2 }; for (let index = 0; index < keys1.length; index += 1) { const key = keys1[index]; if (!keys2.includes(key)) { retObj[key] = null; // eslint-disable-next-line no-continue continue; } delete retObj[key]; } return retObj; } let hasChange = false; const retObj = {}; for (let index = 0; index < keys1.length; index += 1) { const key = keys1[index]; const element1 = obj1[key]; const element2 = obj2[key]; const changed = findDiff(element1, element2); if (changed) { hasChange = true; } if (changed) { retObj[key] = changed; } } return hasChange ? retObj : null; }; console.log( JSON.stringify(findDiff( { a: 1, b: 2, c: { a: ['1', 'b', { a: 'b', c: false }, true], }, }, { a: 1, b: 2, c: { a: ['1','b', { a: 'b', c: true }, true], }, } ), null, 2) );
var base = [ {"value": "01", "label": "Pendências"}, {"value": "02", "label": "Ambulatório"}, {"value": "03", "label": "Urgência"}, {"value": "04", "label": "Clínica Médica"}, {"value": "05", "label": "Revisão"}, {"value": "06", "label": "Imagens"}, ]; var used = [ {"value": "01", "label": "Pendências"}, {"value": "02", "label": "Ambulatório"}, {"value": "03", "label": "Urgência"}, {"value": "04", "label": "Clínica Médica"}, ]; function diff(obj1,obj2) { var temp = JSON.stringify(obj2.map((x)=> x.value)); return obj1.filter((y)=> temp.indexOf(y.value)<0 && y); } var result = diff(base, used); console.clear(); console.log('RESULTADO'); console.log(result); codeped
function Difference (ob1,ob2){ let ob3={} let status=false for ( var a1 in ob1 ) { for (var a2 in ob2){ if (a1===a2 && ob1[a1]===ob2[a2]){ status=true; break; }; }; if (status===false){ if (ob1[a2]===undefined){ ob3[a1]="ob1:"+ob1[a1]+", ob2:"+ob2[a1]; }; if ( ob2[a1]===undefined){ ob3[a2]="ob1:"+ob1[a2]+", ob2:"+ob2[a2]; }else { ob3[a1]="ob1:"+ob1[a1] +", ob2:"+ob2[a1]; }; }else { status=false; }; }; console.log(ob3); };
For a simple object diff I like the very straightforward: function simpleObjectDiff(obj1, obj2) { return Object.fromEntries( Object.entries(obj1) .filter(([key, value]) => value !== obj2[key]) ) }
Comparing items in ojects in two arrays js [duplicate]
I want to compare 2 arrays of objects in JavaScript code. The objects have 8 total properties, but each object will not have a value for each, and the arrays are never going to be any larger than 8 items each, so maybe the brute force method of traversing each and then looking at the values of the 8 properties is the easiest way to do what I want to do, but before implementing, I wanted to see if anyone had a more elegant solution. Any thoughts?
As serialization doesn't work generally (only when the order of properties matches: JSON.stringify({a:1,b:2}) !== JSON.stringify({b:2,a:1})) you have to check the count of properties and compare each property as well: const objectsEqual = (o1, o2) => Object.keys(o1).length === Object.keys(o2).length && Object.keys(o1).every(p => o1[p] === o2[p]); const obj1 = { name: 'John', age: 33}; const obj2 = { age: 33, name: 'John' }; const obj3 = { name: 'John', age: 45 }; console.log(objectsEqual(obj1, obj2)); // true console.log(objectsEqual(obj1, obj3)); // false If you need a deep comparison, you can call the function recursively: const obj1 = { name: 'John', age: 33, info: { married: true, hobbies: ['sport', 'art'] } }; const obj2 = { age: 33, name: 'John', info: { hobbies: ['sport', 'art'], married: true } }; const obj3 = { name: 'John', age: 33 }; const objectsEqual = (o1, o2) => typeof o1 === 'object' && Object.keys(o1).length > 0 ? Object.keys(o1).length === Object.keys(o2).length && Object.keys(o1).every(p => objectsEqual(o1[p], o2[p])) : o1 === o2; console.log(objectsEqual(obj1, obj2)); // true console.log(objectsEqual(obj1, obj3)); // false Then it's easy to use this function to compare objects in arrays: const arr1 = [obj1, obj1]; const arr2 = [obj1, obj2]; const arr3 = [obj1, obj3]; const arraysEqual = (a1, a2) => a1.length === a2.length && a1.every((o, idx) => objectsEqual(o, a2[idx])); console.log(arraysEqual(arr1, arr2)); // true console.log(arraysEqual(arr1, arr3)); // false
EDIT: You cannot overload operators in current, common browser-based implementations of JavaScript interpreters. To answer the original question, one way you could do this, and mind you, this is a bit of a hack, simply serialize the two arrays to JSON and then compare the two JSON strings. That would simply tell you if the arrays are different, obviously you could do this to each of the objects within the arrays as well to see which ones were different. Another option is to use a library which has some nice facilities for comparing objects - I use and recommend MochiKit. EDIT: The answer kamens gave deserves consideration as well, since a single function to compare two given objects would be much smaller than any library to do what I suggest (although my suggestion would certainly work well enough). Here is a naïve implemenation that may do just enough for you - be aware that there are potential problems with this implementation: function objectsAreSame(x, y) { var objectsAreSame = true; for(var propertyName in x) { if(x[propertyName] !== y[propertyName]) { objectsAreSame = false; break; } } return objectsAreSame; } The assumption is that both objects have the same exact list of properties. Oh, and it is probably obvious that, for better or worse, I belong to the only-one-return-point camp. :)
Honestly, with 8 objects max and 8 properties max per object, your best bet is to just traverse each object and make the comparisons directly. It'll be fast and it'll be easy. If you're going to be using these types of comparisons often, then I agree with Jason about JSON serialization...but otherwise there's no need to slow down your app with a new library or JSON serialization code.
I know this is an old question and the answers provided work fine ... but this is a bit shorter and doesn't require any additional libraries ( i.e. JSON ): function arraysAreEqual(ary1,ary2){ return (ary1.join('') == ary2.join('')); }
I have worked a bit on a simple algorithm to compare contents of two objects and return an intelligible list of difference. Thought I would share. It borrows some ideas for jQuery, namely the map function implementation and the object and array type checking. It returns a list of "diff objects", which are arrays with the diff info. It's very simple. Here it is: // compare contents of two objects and return a list of differences // returns an array where each element is also an array in the form: // [accessor, diffType, leftValue, rightValue ] // // diffType is one of the following: // value: when primitive values at that index are different // undefined: when values in that index exist in one object but don't in // another; one of the values is always undefined // null: when a value in that index is null or undefined; values are // expressed as boolean values, indicated wheter they were nulls // type: when values in that index are of different types; values are // expressed as types // length: when arrays in that index are of different length; values are // the lengths of the arrays // function DiffObjects(o1, o2) { // choose a map() impl. // you may use $.map from jQuery if you wish var map = Array.prototype.map? function(a) { return Array.prototype.map.apply(a, Array.prototype.slice.call(arguments, 1)); } : function(a, f) { var ret = new Array(a.length), value; for ( var i = 0, length = a.length; i < length; i++ ) ret[i] = f(a[i], i); return ret.concat(); }; // shorthand for push impl. var push = Array.prototype.push; // check for null/undefined values if ((o1 == null) || (o2 == null)) { if (o1 != o2) return [["", "null", o1!=null, o2!=null]]; return undefined; // both null } // compare types if ((o1.constructor != o2.constructor) || (typeof o1 != typeof o2)) { return [["", "type", Object.prototype.toString.call(o1), Object.prototype.toString.call(o2) ]]; // different type } // compare arrays if (Object.prototype.toString.call(o1) == "[object Array]") { if (o1.length != o2.length) { return [["", "length", o1.length, o2.length]]; // different length } var diff =[]; for (var i=0; i<o1.length; i++) { // per element nested diff var innerDiff = DiffObjects(o1[i], o2[i]); if (innerDiff) { // o1[i] != o2[i] // merge diff array into parent's while including parent object name ([i]) push.apply(diff, map(innerDiff, function(o, j) { o[0]="[" + i + "]" + o[0]; return o; })); } } // if any differences were found, return them if (diff.length) return diff; // return nothing if arrays equal return undefined; } // compare object trees if (Object.prototype.toString.call(o1) == "[object Object]") { var diff =[]; // check all props in o1 for (var prop in o1) { // the double check in o1 is because in V8 objects remember keys set to undefined if ((typeof o2[prop] == "undefined") && (typeof o1[prop] != "undefined")) { // prop exists in o1 but not in o2 diff.push(["[" + prop + "]", "undefined", o1[prop], undefined]); // prop exists in o1 but not in o2 } else { // per element nested diff var innerDiff = DiffObjects(o1[prop], o2[prop]); if (innerDiff) { // o1[prop] != o2[prop] // merge diff array into parent's while including parent object name ([prop]) push.apply(diff, map(innerDiff, function(o, j) { o[0]="[" + prop + "]" + o[0]; return o; })); } } } for (var prop in o2) { // the double check in o2 is because in V8 objects remember keys set to undefined if ((typeof o1[prop] == "undefined") && (typeof o2[prop] != "undefined")) { // prop exists in o2 but not in o1 diff.push(["[" + prop + "]", "undefined", undefined, o2[prop]]); // prop exists in o2 but not in o1 } } // if any differences were found, return them if (diff.length) return diff; // return nothing if objects equal return undefined; } // if same type and not null or objects or arrays // perform primitive value comparison if (o1 != o2) return [["", "value", o1, o2]]; // return nothing if values are equal return undefined; }
I tried JSON.stringify() and worked for me. let array1 = [1,2,{value:'alpha'}] , array2 = [{value:'alpha'},'music',3,4]; JSON.stringify(array1) // "[1,2,{"value":"alpha"}]" JSON.stringify(array2) // "[{"value":"alpha"},"music",3,4]" JSON.stringify(array1) === JSON.stringify(array2); // false
There is a optimized code for case when function needs to equals to empty arrays (and returning false in that case) const objectsEqual = (o1, o2) => { if (o2 === null && o1 !== null) return false; return o1 !== null && typeof o1 === 'object' && Object.keys(o1).length > 0 ? Object.keys(o1).length === Object.keys(o2).length && Object.keys(o1).every(p => objectsEqual(o1[p], o2[p])) : (o1 !== null && Array.isArray(o1) && Array.isArray(o2) && !o1.length && !o2.length) ? true : o1 === o2; }
Here is my attempt, using Node's assert module + npm package object-hash. I suppose that you would like to check if two arrays contain the same objects, even if those objects are ordered differently between the two arrays. var assert = require('assert'); var hash = require('object-hash'); var obj1 = {a: 1, b: 2, c: 333}, obj2 = {b: 2, a: 1, c: 444}, obj3 = {b: "AAA", c: 555}, obj4 = {c: 555, b: "AAA"}; var array1 = [obj1, obj2, obj3, obj4]; var array2 = [obj3, obj2, obj4, obj1]; // [obj3, obj3, obj2, obj1] should work as well // calling assert.deepEquals(array1, array2) at this point FAILS (throws an AssertionError) // even if array1 and array2 contain the same objects in different order, // because array1[0].c !== array2[0].c // sort objects in arrays by their hashes, so that if the arrays are identical, // their objects can be compared in the same order, one by one var array1 = sortArrayOnHash(array1); var array2 = sortArrayOnHash(array2); // then, this should output "PASS" try { assert.deepEqual(array1, array2); console.log("PASS"); } catch (e) { console.log("FAIL"); console.log(e); } // You could define as well something like Array.prototype.sortOnHash()... function sortArrayOnHash(array) { return array.sort(function(a, b) { return hash(a) > hash(b); }); }
My practice implementation with sorting, tested and working. const obj1 = { name: 'John', age: 33}; const obj2 = { age: 33, name: 'John' }; const obj3 = { name: 'John', age: 45 }; const equalObjs = ( obj1, obj2 ) => { let keyExist = false; for ( const [key, value] of Object.entries(obj1) ) { // Search each key in reference object and attach a callback function to // compare the two object keys if( Object.keys(obj2).some( ( e ) => e == key ) ) { keyExist = true; } } return keyExist; } console.info( equalObjs( obj1, obj2 ) ); Compare your arrays // Sort Arrays var arr1 = arr1.sort(( a, b ) => { var fa = Object.keys(a); var fb = Object.keys(b); if (fa < fb) { return -1; } if (fa > fb) { return 1; } return 0; }); var arr2 = arr2.sort(( a, b ) => { var fa = Object.keys(a); var fb = Object.keys(b); if (fa < fb) { return -1; } if (fa > fb) { return 1; } return 0; }); const equalArrays = ( arr1, arr2 ) => { // If the arrays are different length we an eliminate immediately if( arr1.length !== arr2.length ) { return false; } else if ( arr1.every(( obj, index ) => equalObjs( obj, arr2[index] ) ) ) { return true; } else { return false; } } console.info( equalArrays( arr1, arr2 ) );
I am sharing my compare function implementation as it might be helpful for others: /* null AND null // true undefined AND undefined // true null AND undefined // false [] AND [] // true [1, 2, 'test'] AND ['test', 2, 1] // true [1, 2, 'test'] AND ['test', 2, 3] // false [undefined, 2, 'test'] AND ['test', 2, 1] // false [undefined, 2, 'test'] AND ['test', 2, undefined] // true [[1, 2], 'test'] AND ['test', [2, 1]] // true [1, 'test'] AND ['test', [2, 1]] // false [[2, 1], 'test'] AND ['test', [2, 1]] // true [[2, 1], 'test'] AND ['test', [2, 3]] // false [[[3, 4], 2], 'test'] AND ['test', [2, [3, 4]]] // true [[[3, 4], 2], 'test'] AND ['test', [2, [5, 4]]] // false [{x: 1, y: 2}, 'test'] AND ['test', {x: 1, y: 2}] // true 1 AND 1 // true {test: 1} AND ['test', 2, 1] // false {test: 1} AND {test: 1} // true {test: 1} AND {test: 2} // false {test: [1, 2]} AND {test: [1, 2]} // true {test: [1, 2]} AND {test: [1]} // false {test: [1, 2], x: 1} AND {test: [1, 2], x: 2} // false {test: [1, { z: 5 }], x: 1} AND {x: 1, test: [1, { z: 5}]} // true {test: [1, { z: 5 }], x: 1} AND {x: 1, test: [1, { z: 6}]} // false */ function is_equal(x, y) { const arr1 = x, arr2 = y, is_objects_equal = function (obj_x, obj_y) { if (!( typeof obj_x === 'object' && Object.keys(obj_x).length > 0 )) return obj_x === obj_y; return Object.keys(obj_x).length === Object.keys(obj_y).length && Object.keys(obj_x).every(p => is_objects_equal(obj_x[p], obj_y[p])); } ; if (!( Array.isArray(arr1) && Array.isArray(arr2) )) return ( arr1 && typeof arr1 === 'object' && arr2 && typeof arr2 === 'object' ) ? is_objects_equal(arr1, arr2) : arr1 === arr2; if (arr1.length !== arr2.length) return false; for (const idx_1 of arr1.keys()) for (const idx_2 of arr2.keys()) if ( ( Array.isArray(arr1[idx_1]) && this.is_equal(arr1[idx_1], arr2[idx_2]) ) || is_objects_equal(arr1[idx_1], arr2[idx_2]) ) { arr2.splice(idx_2, 1); break; } return !arr2.length; }
Please try this one: function used_to_compare_two_arrays(a, b) { // This block will make the array of indexed that array b contains a elements var c = a.filter(function(value, index, obj) { return b.indexOf(value) > -1; }); // This is used for making comparison that both have same length if no condition go wrong if (c.length !== a.length) { return 0; } else{ return 1; } }
The objectsAreSame function mentioned in #JasonBunting's answer works fine for me. However, there's a little problem: If x[propertyName] and y[propertyName] are objects (typeof x[propertyName] == 'object'), you'll need to call the function recursively in order to compare them.
not sure about the performance ... will have to test on big objects .. however, this works great for me.. the advantage it has compared to the other solutions is, the objects/array do not have to be in the same order .... it practically takes the first object in the first array, and scans the second array for every objects .. if it's a match, it will proceed to another there is absolutely a way for optimization but it's working :) thx to #ttulka I got inspired by his work ... just worked on it a little bit const objectsEqual = (o1, o2) => { let match = false if(typeof o1 === 'object' && Object.keys(o1).length > 0) { match = (Object.keys(o1).length === Object.keys(o2).length && Object.keys(o1).every(p => objectsEqual(o1[p], o2[p]))) }else { match = (o1 === o2) } return match } const arraysEqual = (a1, a2) => { let finalMatch = [] let itemFound = [] if(a1.length === a2.length) { finalMatch = [] a1.forEach( i1 => { itemFound = [] a2.forEach( i2 => { itemFound.push(objectsEqual(i1, i2)) }) finalMatch.push(itemFound.some( i => i === true)) }) } return finalMatch.every(i => i === true) } const ar1 = [ { id: 1, name: "Johnny", data: { body: "Some text"}}, { id: 2, name: "Jimmy"} ] const ar2 = [ {name: "Jimmy", id: 2}, {name: "Johnny", data: { body: "Some text"}, id: 1} ] console.log("Match:",arraysEqual(ar1, ar2)) jsfiddle: https://jsfiddle.net/x1pubs6q/ or just use lodash :)))) const _ = require('lodash') const isArrayEqual = (x, y) => { return _.isEmpty(_.xorWith(x, y, _.isEqual)); };
using _.some from lodash: https://lodash.com/docs/4.17.11#some const array1AndArray2NotEqual = _.some(array1, (a1, idx) => a1.key1 !== array2[idx].key1 || a1.key2 !== array2[idx].key2 || a1.key3 !== array2[idx].key3);
There`s my solution. It will compare arrays which also have objects and arrays. Elements can be stay in any positions. Example: const array1 = [{a: 1}, {b: 2}, { c: 0, d: { e: 1, f: 2, } }, [1,2,3,54]]; const array2 = [{a: 1}, {b: 2}, { c: 0, d: { e: 1, f: 2, } }, [1,2,3,54]]; const arraysCompare = (a1, a2) => { if (a1.length !== a2.length) return false; const objectIteration = (object) => { const result = []; const objectReduce = (obj) => { for (let i in obj) { if (typeof obj[i] !== 'object') { result.push(`${i}${obj[i]}`); } else { objectReduce(obj[i]); } } }; objectReduce(object); return result; }; const reduceArray1 = a1.map(item => { if (typeof item !== 'object') return item; return objectIteration(item).join(''); }); const reduceArray2 = a2.map(item => { if (typeof item !== 'object') return item; return objectIteration(item).join(''); }); const compare = reduceArray1.map(item => reduceArray2.includes(item)); return compare.reduce((acc, item) => acc + Number(item)) === a1.length; }; console.log(arraysCompare(array1, array2));
This is work for me to compare two array of objects without taking into consideration the order of the items const collection1 = [ { id: "1", name: "item 1", subtitle: "This is a subtitle", parentId: "1" }, { id: "2", name: "item 2", parentId: "1" }, { id: "3", name: "item 3", parentId: "1" }, ] const collection2 = [ { id: "3", name: "item 3", parentId: "1" }, { id: "2", name: "item 2", parentId: "1" }, { id: "1", name: "item 1", subtitle: "This is a subtitle", parentId: "1" }, ] const contains = (arr, obj) => { let i = arr.length; while (i--) { if (JSON.stringify(arr[i]) === JSON.stringify(obj)) { return true; } } return false; } const isEqual = (obj1, obj2) => { let n = 0 if (obj1.length !== obj2.length) { return false; } for (let i = 0; i < obj1.length; i++) { if (contains(obj2, obj1[i])) { n++ } } return n === obj1.length } console.log(isEqual(collection1,collection2)) if you take into consideration the order of the items use built in function in lodash isEqual
comparing with json is pretty bad. try this package to compare nested arrays and get the difference. https://www.npmjs.com/package/deep-object-diff
If you stringify them... type AB = { nome: string; } const a: AB[] = [{ nome: 'Célio' }]; const b: AB[] = [{ nome: 'Célio' }]; console.log(a === b); // false console.log(JSON.stringify(a) === JSON.stringify(b)); // true
Merging two objects with same keys override first array
I would like to merge an array with another array. The only catch is that each array is within an object. Intuitively I tried {...arrObj, ...newArrObj} however this leads newArrObj overwriting items in the arrObj. const array = ['an', 'array']; const newArray = [, , 'new', 'ehrray']; const obj = { key: { ...array } }; const newObj = { key: { ...newArray } }; const merged = { ...obj, ...newObj }; console.log(merged); I would expect merged to be: { "key": { "0": "an", "1": "array", "2": "new", "3": "ehrray" } } but receive { "key": { "2": "new", "3": "ehrray" } }
This might be useful const a0 = ['1', '2', undefined , undefined, '5', '6', '7']; const a1 = [undefined, undefined, '3', '4']; function merge(a, b) { return a.map(function(v,i){ return v?v:b[i]}); } console.log(a0 > a1?merge(a0, a1):merge(a1, a0));
I wanted to updated that I ended up going with a recursive merge to get the nested object containing an array merged. const array = ['an', 'array']; const newArray = [, , 'new', 'ehrray']; const obj = { key: { ...array } }; const newObj = { key: { ...newArray } }; const merge = (obj1, obj2) => { const recursiveMerge = (obj, entries) => { for (const [key, value] of entries) { if (typeof value === "object") { obj[key] = obj[key] ? { ...obj[key] } : {}; recursiveMerge(obj[key], Object.entries(value)) } else { obj[key] = value; } } return obj; } return recursiveMerge(obj1, Object.entries(obj2)) } console.log(merge(obj, newObj));
The idea is that there are unset values with only a few set. eg. const newArray = new Array(4); newArray[2] = 'new'; { value: null }, even { value: undefined } is not the same thing as { foo: 42 } with no value at all. That's the reason that in your example "an" and "array" are overwritten with the nulls from the newArray. This particular example you can solve by swapping the order in which you add the arrays to the result, but as soon as both arrays contain null-values there is no way to do it with spread-syntax / Object.assign alone. You have to implement the behaviour: const array = new Array('an', 'array', null, null, "and", "more", "from", "array"); const newArray = new Array(null, null, 'new', 'ehrray'); function merge(a, b) { const result = []; for (let i = 0; i < a.length || i < b.length; ++i) { result[i] = b[i] == null ? a[i] : b[i]; } return result; } console.log(merge(array, newArray));
How to merge array of objects in one object, including inner objects - JavaScript?
Suppose I have the following array of objects: var list = [ { a: 1, b: { c: 'x', k: []} }, { a: 1, b: {c: 'x', d: 8} } ]; I want them to be merged into one "generic" object, for this example, it would be: {a: 1, b: {c: 'x', d:'8', k[]}} As you can see, all nested objects are merged too. But I can't gain it. If I use Object.assign it creates new nested objects if they are different, that is duplicates them: var res = Object.assign({}, ...list); // res: { a: 1, b: {c: 'x', k: []}, b: {c: 'x', d: 8} }
You could try the following using the reduce method: var list = [{ a: 1, b: { a: 4, k: 3 } }, { a: 1, s: 11, b: { ab: 4, d: 8 } }] var result = list.reduce(function(acc, item) { var obj = { ...item } Object.keys(obj).forEach(function(item) { if (acc[item]) { //if a property with the the key, 'item' already exists, then append to that Object.assign(acc[item], obj[item]); } else { // else add the key-value pair to the accumulator object. acc[item] = obj[item]; } }) return acc; }, {}) console.log(result);
Deep merging is not simple to do yourself, That blog uses deep merge. If you don't have webpack or nodejs you can use deepmerge like so: // see https://github.com/facebook/react/blob/b5ac963fb791d1298e7f396236383bc955f916c1/src/isomorphic/classic/element/ReactElement.js#L21-L25 var canUseSymbol = typeof Symbol === 'function' && Symbol.for var REACT_ELEMENT_TYPE = canUseSymbol ? Symbol.for('react.element') : 0xeac7 function isReactElement(value) { return value.$$typeof === REACT_ELEMENT_TYPE } function isNonNullObject(value) { return !!value && typeof value === 'object' } function isSpecial(value) { var stringValue = Object.prototype.toString.call(value) return stringValue === '[object RegExp]' || stringValue === '[object Date]' || isReactElement(value) } function defaultIsMergeableObject(value) { return isNonNullObject(value) && !isSpecial(value) } function emptyTarget(val) { return Array.isArray(val) ? [] : {} } function cloneUnlessOtherwiseSpecified(value, options) { return (options.clone !== false && options.isMergeableObject(value)) ? deepmerge(emptyTarget(value), value, options) : value } function defaultArrayMerge(target, source, options) { return target.concat(source).map(function(element) { return cloneUnlessOtherwiseSpecified(element, options) }) } function mergeObject(target, source, options) { var destination = {} if (options.isMergeableObject(target)) { Object.keys(target).forEach(function(key) { destination[key] = cloneUnlessOtherwiseSpecified(target[key], options) }) } Object.keys(source).forEach(function(key) { if (!options.isMergeableObject(source[key]) || !target[key]) { destination[key] = cloneUnlessOtherwiseSpecified(source[key], options) } else { destination[key] = deepmerge(target[key], source[key], options) } }) return destination } function deepmerge(target, source, options) { options = options || {} options.arrayMerge = options.arrayMerge || defaultArrayMerge options.isMergeableObject = options.isMergeableObject || defaultIsMergeableObject var sourceIsArray = Array.isArray(source) var targetIsArray = Array.isArray(target) var sourceAndTargetTypesMatch = sourceIsArray === targetIsArray if (!sourceAndTargetTypesMatch) { return cloneUnlessOtherwiseSpecified(source, options) } else if (sourceIsArray) { return options.arrayMerge(target, source, options) } else { return mergeObject(target, source, options) } } deepmerge.all = function deepmergeAll(array, options) { if (!Array.isArray(array)) { throw new Error('first argument should be an array') } return array.reduce(function(prev, next) { return deepmerge(prev, next, options) }, {}) } var list = [{ a: 1, b: { c: 'x', //merging 1,2 and 1,3 results in [1,2,1,3] you can change that in defaultArrayMerge k: [1,2] } }, { a: 1, b: { c: 'x', k: [1,3], d: 8 } }]; console.log( deepmerge.all(list) )
You can use the reduce method. Remove the first element from the original list , that object will be the base method. var list = [{ a: 1, b: { c: 'x', k: [] } }, { a: 1, b: { c: 'x', d: 8 } } ]; // Remove the first element from the array. The first element will be // the base object // slice will return a new array without the first object // apply reduce on this list let _temp = list.slice(1); let x = _temp.reduce(function(acc,curr,currIndex){ for(let keys in curr){ // checking if the base object have the same key as of current object if(acc.hasOwnProperty(keys)){ // if base object and current object has the key then // check if the type is an object if(typeof curr[keys] ==='object'){ // now get the key from both the object // & check which one is missong. Add that key and value to the // base object let keysFromACC = Object.keys(acc[keys]); let keysFromCURR = Object.keys(curr[keys]); keysFromCURR.forEach(function(item){ if(keysFromACC.indexOf(item) ===-1){ acc[keys][item] = curr[keys][item] } }) } } else{ // if the base object does not have key which current object // has then add the key to base object acc[keys]= curr[keys] } } return acc; },list[0]); console.log(x)
Remove empty objects from an object
I am trying to remove empty objects inside an object, here is an example with the expected output: var object = { a: { b: 1, c: { a: 1, d: {}, e: { f: {} } } }, b: {} } var expectedResult = { a: { b: 1, c: { a: 1, } } } I tried using some examples from other StackOverflow questions, however those are just for one level objects.
Basic function that removes empty objects First start with a function that only works with a single level of nesting. This function removes all properties that reference an empty object: function clearEmpties(o) { for (var k in o) { if (!o[k] || typeof o[k] !== "object") { continue // If null or not an object, skip to the next iteration } // The property is an object if (Object.keys(o[k]).length === 0) { delete o[k]; // The object had no properties, so delete that property } return o; } } Handling nested objects using recursion Now you want to make it recursive so that it will operate on nested objects. So we already have tested if o[k] is an object, and we've tested if there are properties, so if there are, we simply call the function again with that nested object. function clearEmpties(o) { for (var k in o) { if (!o[k] || typeof o[k] !== "object") { continue // If null or not an object, skip to the next iteration } // The property is an object clearEmpties(o[k]); // <-- Make a recursive call on the nested object if (Object.keys(o[k]).length === 0) { delete o[k]; // The object had no properties, so delete that property } } return o; } So just as the original call to clearEmpties removes properties of the given object that reference an empty object, likewise the recursive call will do the same for the nested objects. Live demo: var object = { a: { b: 1, c: { a: 1, d: {}, e: { // will need to be removed after f has been removed f: {} } } }, b: {} }; clearEmpties(object); console.log(object); function clearEmpties(o) { for (var k in o) { if (!o[k] || typeof o[k] !== "object") { continue } clearEmpties(o[k]); if (Object.keys(o[k]).length === 0) { delete o[k]; } } return o; } Short version using Underscore and functional style function clearEmpties(o) { if (_.isFunction(o) || !_.isObject(o)) return o; return _.chain(o) .mapObject(clearEmpties) .pick(p => !(_.isObject(p) && _.isEmpty(p))) .value(); } Short version using lodash and functional style - works with treeshaking import { isFunction, isObject, isEmpty, isArray, isPlainObject, fromPairs } from "lodash-es"; const removeEmtpyObjects = (o) => { if (isFunction(o) || !isPlainObject(o)) return o; if (isArray(o)) return o.map(removeEmtpyObjects); return fromPairs( Object.entries(o) .map(([k, v]) => [k, removeEmtpyObjects(v)]) .filter(([k, v]) => !(v == null || (isObject(v) && isEmpty(v)))) ); };
I had this same problem and with the addition that my object may contain arrays with empty elements that need to be removed as well. I ended up with this quick and dirty solution. If you want to define what "empty" means to you, I also added a different function. In my case, I also needed empty strings. function isEmpty(obj) { if (obj === '' || obj === null || JSON.stringify(obj) === '{}' || JSON.stringify(obj) === '[]' || (obj) === undefined || (obj) === {}) { return true } else { return false } } function removeEmpty(o) { if (typeof o !== "object") { return o; } let oKeys = Object.keys(o) for (let j = 0; j < oKeys.length; j++) { let p = oKeys[j] switch (typeof (o[p])) { case 'object': if (Array.isArray(o[p])) { for (let i = 0; i < o[p].length; i++) { o[p][i] = removeEmpty(o[p][i]) if (isEmpty(o[p][i])) { o[p].splice(i, 1) i-- } } if (o[p].length === 0) { if (Array.isArray(o)) { o.splice(parseInt(p), 1) j-- } else { delete o[p] } } } else { if (isEmpty(o[p])) { delete o[p] } else { o[p] = removeEmpty(o[p]) if (isEmpty(o[p])) { delete o[p] } } } break default: if (isEmpty(o[p])) { delete o[p] } break } } if (Object.keys(o).length === 0) { return } return o } Input: var a = { b: 1, c: { d: [1, [[], [], [[[1], []]]], [2, [[], [[]]]], [], [[]]] }, e: { f: [{}, { g: 1 }] }, h: { i: { j: { k: undefined, l: null, m: { n: "", o: 1 } } } }, p: { q: { r: 1 } } } removeEmpty(a) Output: { "b": 1, "c": { "d": [1, [[[[1]]]], [2]] }, "e": { "f": [{"g": 1}] }, "h": { "i": { "j": { "m": { "o": 1 } } } }, "p": { "q": { "r": 1 } } }
function clean(obj) { for (var propName in obj) { if (obj[propName] === null || obj[propName] === undefined) { delete obj[propName]; } } } EDIT: function clean(obj) { for (var propName in obj) { if(typeof obj[propName]=="object") clean(obj[propName]) if (obj[propName] === null || obj[propName] === undefined) delete obj[propName]; } }