I have two nested objects obj1
and obj2
and I want to compare them and the recursively return an object that for each nested key has a equality-lik
A recursive example,
var obj1 = {
prop1: 1,
prop2: "foo",
prop3: {
prop4: 2,
prop5: "bar"
},
prop7: {},
}
var obj2 = {
prop1: 3,
prop2: "foo",
prop3: {
prop4: 2,
prop5: "foobar"
},
prop6: "new",
prop7: {},
prop8: {},
}
var result = {};
function compare(obj1, obj2, obj_) {
for (let k in obj1) {
var type = typeof obj1[k];
if (type === 'object') {
obj_[k] = {};
if (!obj2[k]){
obj_[k] = false;
}else if ((Object.entries(obj1[k]).length === 0 && obj1[k].constructor === Object) && (Object.entries(obj2[k]).length === 0 && obj2[k].constructor === Object)) {
obj_[k] = true;
} else {
compare(obj1[k], obj2[k], obj_[k]);
}
} else {
obj_[k] = (obj1[k] === obj2[k]);
}
}
}
if (Object.keys(obj1).length < Object.keys(obj2).length) { //check if both objects vary in length.
var tmp = obj1;
obj1 = obj2;
obj2 = tmp;
}
compare(obj1, obj2, result);
console.log(result);
You could iterate all keys and check the nested objects if both values are objects.
const isObject = v => v && typeof v === 'object';
function getDifference(a, b) {
return Object.assign(...Array.from(
new Set([...Object.keys(a), ...Object.keys(b)]),
k => ({ [k]: isObject(a[k]) && isObject(b[k])
? getDifference(a[k], b[k])
: a[k] === b[k]
})
));
}
var obj1 = { prop1: 1, prop2: "foo", prop3: { prop4: 2, prop5: "bar" } },
obj2 = { prop1: 3, prop2: "foo", prop3: { prop4: 2, prop5: "foobar" }, prop6: "new" };
console.log(getDifference(obj1, obj2));
Here is a solution that I made recently that can handle the same issue, and it takes an optional key for setting comparison strictness. Useful for when your backend sends a value as a number, but expects that value to be returned as a string. We had been using JSON.stringify comparison, but it is a bit crude, and cant account for when the objects are the same but the keys are in different order.
public objectsAreTheSame(objA: {[key: string]: any}, objB: {[key: string]: any}, isStrict = false): boolean {
let areTheSame = true;
const strictLevel = isStrict ? 'isStrict' : 'isNotStrict';
const valuesDoNotMatch = {
'isStrict': (a, b) => a !== b,
'isNotStrict': (a, b) => a != b
};
const isObject = (a, b) => typeof a === 'object' && !Array.isArray(a) && (!!a && !!b);
const compareArrays = (a, b) => {
if (a.length === b.length) {
a.sort();
b.sort();
a.forEach((ele, idx) => compareValues(ele, b[idx]));
} else {
areTheSame = false;
}
};
const compareValues = (a, b) => {
if (Array.isArray(a)) {
compareArrays(a, b);
} else if (!isObject(a, b) && valuesDoNotMatch[strictLevel](a, b)) {
areTheSame = false;
} else if (isObject(a, b) && !this.objectsAreTheSame(a, b, isStrict)) {
areTheSame = false;
}
};
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) return false;
for (let key of keysA) compareValues(objA[key], objB[key]);
return areTheSame;
}
You could use reduce
to build new object and another get
method to get nested props from other object by string
and compare it to current prop value in first object.
const obj1 = { prop1: 1, prop2: "foo", prop3: { prop4: 2, prop5: "bar" } }
const obj2 = { prop1: 3, prop2: "foo", prop3: { prop4: 2, prop5: "foobar" } }
function get(obj, path) {
return path.split('.').reduce((r, e) => {
if (!r) return r
else return r[e] || undefined
}, obj)
}
function compare(a, b, prev = "") {
return Object.keys(a).reduce((r, e) => {
const path = prev + (prev ? '.' + e : e);
const value = a[e] === get(b, path);
r[e] = typeof a[e] === 'object' ? compare(a[e], b, path) : value
return r;
}, {})
}
const result = compare(obj1, obj2);
console.log(result)
To compare all properties of both objects you could create extra function that will perform loop by both objects.
const obj1 = {"prop1":1,"prop2":"foo","prop3":{"prop4":2,"prop5":"bar"},"prop7":{"prop9":{"prop10":"foo"}}}
const obj2 = {"prop1":3,"prop2":"foo","prop3":{"prop4":2,"prop5":"foobar"},"prop6":"new","prop7":{"foo":"foo","bar":{"baz":"baz"}}}
function get(obj, path) {
return path.split('.').reduce((r, e) => {
if (!r) return r;
else return r[e] || undefined;
}, obj);
}
function isEmpty(o) {
if (typeof o !== 'object') return true;
else return !Object.keys(o).length;
}
function build(a, b, o = null, prev = '') {
return Object.keys(a).reduce(
(r, e) => {
const path = prev + (prev ? '.' + e : e);
const bObj = get(b, path);
const value = a[e] === bObj;
if (typeof a[e] === 'object') {
if (isEmpty(a[e]) && isEmpty(bObj)) {
if (e in r) r[e] = r[e];
else r[e] = true;
} else if (!bObj && isEmpty(a[e])) {
r[e] = value;
} else {
r[e] = build(a[e], b, r[e], path);
}
} else {
r[e] = value;
}
return r;
},
o ? o : {}
);
}
function compare(a, b) {
const o = build(a, b);
return build(b, a, o);
}
const result = compare(obj1, obj2);
console.log(result)
You could create a merged
object which will have keys of both object. Loop through this object and compare the values for both obj1
and obj2
for each key. If the property is an object, recursively compare the properties. This will work for any level of nesting. Since the properties could be missing from either of the objects, default parameter = {}
is added.
const obj1={prop1:1,prop2:"foo",prop3:{prop4:2,prop5:"bar"},prop7:{pro8:"only in 1"}},
obj2={prop1:3,prop2:"foo",prop3:{prop4:2,prop5:"foobar"}, prop6: "only in 2"};
const isObject = val => typeof val === 'object' && val // required for "null" comparison
function compare(obj1 = {}, obj2 = {}) {
const output = {},
merged = { ...obj1, ...obj2 }; // has properties of both
for (const key in merged) {
const value1 = obj1[key],
value2 = obj2[key];
if (isObject(value1) || isObject(value2))
output[key] = compare(value1, value2) // recursively call
else
output[key] = value1 === value2
}
return output;
}
console.log(compare(obj1, obj2))