Why my 2 object deep comparison is failing? - javascript

I am trying to compare 2 objects using deep comparison and while comparison i want to ignore some properties.
My comparison is successful when I have those ignore properties on both the side of object.
But I am getting problem when I have 1 property missing in 2nd object which I want to ignore.
In my objA and objB, I want to ignore isParent and location property but as I don't have location property in objB, my object comparison is failing.
But I don't understand why I am getting false as I have specified location property to ignore.
var objA = {
isParent: true,
foo: {
location: "abc",
bar: "foobar"
}
};
var objB = {
isParent: false,
foo: {
bar: "foobar"
}
};
var comparator = function(left, right, key) {
if (key === 'isParent' || key === 'location') return true;//ignore isParent and location property while comparing 2 object
else return undefined;
}
var isEqual = _.isEqualWith(objA, objB, comparator);
console.log(isEqual); // true
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>

Use the omit function to ignore unwanted properties then compare
var objA = {
isParent: true,
foo: {
location: "abc",
bar: "foobar"
}
};
var objB = {
isParent: false,
foo: {
bar: "foobar"
}
};
var isEqual = _.isEqual(
_.omit(objA, ['isParent', 'foo.location']),
_.omit(objB, ['isParent', 'foo.location'])
);
console.log(isEqual); // true
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>

You can write your own compare function:
Logic:
Create a function that takes 2 objects that will be compared and an array(ignoreKeys) of keys that are to be ignored.
Get all keys from both object, merge them and then filter them into a new variable (say keys).
If the current key exists in keys, ignore it.
If the current key exists in ignoreKeys, ignore it
Else push it.
Now loop over these keys and check for comparison:
If current value is of type 'object', use recursion and start the process again.
Else, compare the values and return the comparison.
Since this has to be done for all the keys in keys, you can use Array.every.
Sample
function compareObject(obj1, obj2, ignoreProps){
var temp = Object.keys(obj1).concat(Object.keys(obj2)).sort();
var keys = temp.reduce(function(p,c) {
if(p.indexOf(c) < 0 && ignoreProps.indexOf(c) < 0) {
p.push(c);
}
return p;
}, []);
return keys.every(function(key){
var t1 = typeof(obj1[key])
var t2 = typeof(obj2[key])
if(t1 === t1) {
switch(t1) {
case 'object':
if(obj1[key] !== null && obj2[key] !== null)
return compareObject(obj1[key], obj2[key], ignoreProps);
else
return obj1[key] === obj2[key];
default: return obj1[key] === obj2[key];
}
}
})
}
var objA = {
isParent: true,
foo: {
location: "abc",
bar: "foobar",
test: {
location: 'bla',
test1: {
location: 'bla bla',
value: null
}
}
}
};
var objB = {
isParent: false,
foo: {
bar: "foobar",
test: {
location: 'new',
test1: {
location: 'new new',
value: null
}
}
}
};
var ignoreProperties = ['isParent', 'location'];
console.log(compareObject(objA, objB, ignoreProperties));

You could take all keys of the given objects and iterate and check if either
is a key of a no value check (ignore),
has same values or
both values are truthy and objects and the call of check returns a truthy value.
The keys of the properties to ignore are collected in an object.
function check(o, p) {
var keys = [...new Set(Object.keys(o).concat(Object.keys(p)))];
return keys.every(k => noValueCheck[k]
|| o[k] === p[k]
|| o[k] && p[k] && typeof o[k] === 'object' && typeof p[k] === 'object' && check(o[k], p[k])
);
}
var noValueCheck = { isParent: true, location: true },
objA = { isParent: true, foo: { location: "abc", bar: "foobar" } },
objB = { isParent: false, foo: { bar: "foobar" } };
console.log(check(objA, objB));

Related

Javascript deep copy without breaking dates

I would like to make a deep copy (break references) without turning date objects into strings, how would I do that?
let a = [{
date: new Date()
}, {
name: 'John'
}];
// let b = a.slice(); // This copies the object reference, which is wrong (we want a COPY of the obejcts)
// let b = [...a]; // This copies the object reference, which is wrong (we want a COPY of the obejcts)
// let b = JSON.parse(JSON.stringify(a)); // This makes the date a string, which is wrong (we want date objects, not strings)
let b = a.slice();
a[1].name = 'PETER';
console.log(a);
// [ { date: 2020-06-08T09:10:32.530Z }, { name: 'PETER' } ]
console.log(b);
// [ { date: 2020-06-08T09:10:32.530Z }, { name: 'PETER' } ]
Here is a good answer on Javascript Deep Copying: Copy array by value
This will deep copy the object, let me know if this resolves your issue:
let a = [{ date: new Date() }, { name: 'John'}];
let b = a.map(k=>({...k}));
a[1]="PETER";
console.log(a);
console.log(b);
Here is a utility to deep copy the objects. Hope this helps
const deepCopy = (objectToBeCloned) => {
let resultObj, value, key
if (typeof objectToBeCloned !== "object" || objectToBeCloned === null) {
return objectToBeCloned
}
if(typeof objectToBeCloned === "object") {
if(objectToBeCloned.constructor.name !== "Object") {
resultObj = new objectToBeCloned.constructor(objectToBeCloned)
} else {
resultObj = Array.isArray(objectToBeCloned) ? [] : {}
}
}
for (key in objectToBeCloned) {
value = objectToBeCloned[key]
// Recursively copy for nested objects & arrays
resultObj[key] = deepCopy(value)
}
return resultObj
}
let a = [{
date: new Date(),
x: {y:{z:1}},
test: [1,2,3,4],
testArrObj: [{x:1, y:2}]
}, {
name: 'John'
}];
let b = deepCopy(a)
a[1].name = "PETER";
console.log(b);
console.log(a);
console.log(a === b)
console.log(b === b)
Basic deep clone with date + timezone support written in Typescript (remove the types if not required).
export function clone<T>(x: T) {
// JSON to string
var to = (k: string, v: any) => {
// Serialise date with timezone
if (v && v instanceof Date) {
return v.toUTCString()
}
return v
}
// String to JSON
var from = (k: string, v: any) => {
// Restore date
if (v && typeof v == "string" && v.indexOf("Z") > -1 && moment(v).isValid()) {
return new Date(v)
}
return v
}
return JSON.parse(JSON.stringify(x, to), from) as T
}
let a = [{
date: new Date(),
x: {y:{z:1}},
test: [1,2,3,4],
testArrObj: [{x:1, y:2}]
}, {
name: 'John'
}];
let b = clone(a)
a[1].name = "PETER";
console.log(b);
console.log(a);
console.log(a === b)
console.log(b === b)
Note: An improvement would be to remove the moment dependency.

How to flatten an object with nested objects in javascript

I have some attributes from a nested object that is inside the parent object but I would like to merge nested object with the parent object to be flatten.
Original object:
enrollment = {
user: {
id: 'string',
name: 'string'
},
finished: 'boolean',
path: 'string'
}
expected flatten object:
user: {
id: 'string',
name: 'string',
finished: 'boolean',
path: 'string'
}
You can recursively build object any number of nested objects. So, this function is not your case dependent:
var enrollment = {
user: {
id: 'string',
name: 'string'
},
finished: 'boolean',
path: 'boolean'
}
var enrollment2 = {
user: {
id: 'string',
name: 'string'
},
test: {
test1: {
test2: {
val0:'val0',
test4: { //3rd level nested object for example
val1: 'val1',
val2: 'val2'
}
}
}
},
finished: 'boolean',
path: 'boolean'
}
const flat = (obj, out) => {
Object.keys(obj).forEach(key => {
if (typeof obj[key] == 'object') {
out = flat(obj[key], out) //recursively call for nesteds
} else {
out[key] = obj[key] //direct assign for values
}
})
return out
}
console.log(flat(enrollment, {}))
console.log(flat(enrollment2, {}))
I needed something that avoids rewriting keys with the same name that were in different levels in the original object. So I wrote the following:
const flattenObject = (obj, parentKey = '') => {
if (parentKey !== '') parentKey += '.';
let flattened = {};
Object.keys(obj).forEach((key) => {
if (typeof obj[key] === 'object' && obj[key] !== null) {
Object.assign(flattened, flattenObject(obj[key], parentKey + key))
} else {
flattened[parentKey + key] = obj[key]
}
})
return flattened;
}
var test = {
foo: 'bar',
some: 'thing',
father: {
son1: 'son1 value',
son2: {
grandchild: 'grandchild value',
duplicatedKey: 'note this is also used in first level',
},
},
duplicatedKey: 'note this is also used inside son2',
}
let flat = flattenObject(test);
console.log(flat);
// how to access the flattened keys:
let a = flat['father.son2.grandchild'];
console.log(a);
Also checks if the object is null, as I was having some problems with that in my usage.
Here's a quick and dirty way to flatten your object:
var enrollment = {
user: {
id: 'string',
name: 'string',
},
fineshed: true,
path: false,
};
var user = Object.assign(enrollment.user);
user.fineshed = enrollment.fineshed;
user.path = enrollment.path;
For a generic method with a couple of caveats of no shared key names and only flattening 1 level of depth:
var enrollment = {
user: {
id: 'string',
name: 'string',
},
fineshed: true,
path: false,
};
const flatten = (object) => {
let value = {};
for (var property in object) {
if (typeof object[property] === 'object') {
for (var p in object[property]) {
value[p] = object[property][p];
}
} else {
value[property] = object[property];
}
}
return value;
};
let user = flatten(enrollment);
console.log(user);
using recursion and reduce.
note that if value itself is an array containing objects, you might want add another check like !Array.isArray(value) depending on your case
function flatObj(obj) {
return Object.entries(obj).reduce(
(flatted, [key, value]) =>
typeof value == "object"
? { ...flatted, ...flatObj(value) }
: { ...flatted, [key]: value },
{}
);
}
Just want a single Object:
const enrollment = {
user: {
id: 'string',
name: 'string'
},
finished: 'boolean',
path: 'boolean'
}
function propsToUser(enrollObj){
const u = {...enrollObj.user};
for(let i in enrollObj){
if(i !== 'user')u[i] = enrollObj[i];
}
return u;
}
const user = propsToUser(enrollment);
console.log(user);
Below code snippet takes nested input object like this :
{
name:'Namig',
surname:'Hajiyev',
address:{
city:'Sumgait',
country:'Azerbaijan',
geo: {
lat:'40.5897200',
long:'49.6686100'
}
}
}
and returns result flattened object like this:
{
"name": "Namig",
"surname": "Hajiyev",
"address.city": "Sumgait",
"address.country": "Azerbaijan",
"address.geo.lat": "40.5897200",
"address.geo.long": "49.6686100"
}
Here is my code :
function flattenObject(obj, newObj, prefix) {
newObj = newObj || {};
prefix = prefix || "";
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
const type = typeof obj[key];
const newKey = !!prefix ? prefix + "." + key : key;
if (type === "string") {
newObj[newKey] = obj[key];
}
else if (type === "object") {
flattenObject(obj[key], newObj, newKey);
}
}
}
return newObj;
}
var obj = {
name:'Namig',
surname:'Hajiyev',
address:{
city:'Sumgait',
country:'Azerbaijan',
geo: {
lat:'40.5897200',
long:'49.6686100'
}
}
}
console.log(flattenObject(obj));

Compare nested objects in JavaScript and return keys equality

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-like boolean flag
So for a given obj1 like
obj1 = {
prop1: 1,
prop2: "foo",
prop3: {
prop4: 2,
prop5: "bar"
}
}
and the obj2 like
obj2 = {
prop1: 3,
prop2: "foo",
prop3: {
prop4: 2,
prop5: "foobar"
},
prop6: "new"
}
it should return
equality = {
prop1: false,
prop2: true,
prop3 : {
prop4: true,
prop5: false
},
prop6: false
}
If an object has a new property, like obj2.prop6, then the equality will be equality.prop6 = false.
For non-nested object a simple keys comparison solutions is here Get the property of the difference between two objects in javascript
While to recursively compare nested objects it is showed here JavaScript: Deep comparison recursively: Objects and properties
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));
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))
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);
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;
}

how to re position the key of an object in a JSON using jquery or javascript

I have two objects which I need to compare. However there is a problem in the position of the keys in an object.
var obj1 = [{
Name : abc;
age : 23;
}, {
Name : def;
age : 222;
}]
var obj2 = [{
age : 23;
Name: abc;
}, {
age : 222;
Name : def;
}]
I have tried using the below, but it failed.
(JSON.stringify(obj1) == JSON.stringify(obj2))
Is there a way to swap the position of the keys in an object which iterates throughout the object? I am expecting something like this
var obj1 = [{
Name : abc;
age : 23;
}, {
Name : def;
age : 222;
}]
var obj2 = [{
Name : abc;
age : 23;
}, {
Name : def;
age : 222;
}]
The keys of an object (unordered) is different from the index of an array (ordered). To compare two arrays of objects and see if they have the same object (with the same key/value pairs) in each of the array elements, you can try this:
function compareArrayOfObjects(array1, array2) {
return array1.reduce(function(bool, obj, index) {
return bool && array2[index] && isEqual(obj, array2[index]);
}, array1.length === array2.length);
}
If you simply need to compare values as simple as numbers and strings, you can implement isEqual() as follows:
function isEqual(a, b) {
var aKeys = Object.getOwnPropertyNames(a);
var bKeys = Object.getOwnPropertyNames(b);
return aKeys.reduce(function(bool, aKey) {
return bool && (a[aKey] === b[aKey]);
}, aKeys.length === bKeys.length);
}
If you need deep object comparisons then you probably want a LoDash implementation of _.isEqual().
To put it all together, you will have
var obj1 = [{
Name: 'abc',
age: 23
}, {
Name: 'def',
age: 222
}];
var obj2 = [{
age: 23,
Name: 'abc'
}, {
age: 222,
Name: 'def'
}];
var obj3 = [{}, {}];
function compareArrayOfObjects(array1, array2) {
return array1.reduce(function(bool, obj, index) {
return bool && array2[index] && isEqual(obj, array2[index]);
}, array1.length === array2.length);
}
function isEqual(a, b) {
var aKeys = Object.getOwnPropertyNames(a);
var bKeys = Object.getOwnPropertyNames(b);
return aKeys.reduce(function(bool, aKey) {
return bool && (a[aKey] === b[aKey]);
}, aKeys.length === bKeys.length);
}
console.log('obj1 and obj2 is the same (expected true): ' + compareArrayOfObjects(obj1, obj2));
console.log('obj1 and obj3 is the same (expected false): ' + compareArrayOfObjects(obj1, obj3));
As #RoryMcCrossan said -
The order of the keys in an object is not guaranteed
You should use this code instead
var obj1 = [{
Name: 'abc',
age: 23
}, {
Name: 'def',
age: 222
}];
var obj2 = [{
age: 23,
Name: 'abc'
}, {
age: 222,
Name: 'def'
}];
function isEqual(objA, objB) {
if (objA.length !== objB.length)
return false;
let isequal = true;
for (let i = 0; i < objA.length; i++) {
if (objA[i].Name !== objB[i].Name || objA[i].age !== objB[i].age)
isequal = false;
}
return isequal;
}
if(isEqual(obj1, obj2)){
console.log("Equal");
}else{
console.log("Not equal");
}
The order of the keys in an object is not guaranteed, so any attempt to rearrange them would be redundant.
However given your description, this seems to be an XY problem, as your goal is to compare the objects. The issue you have is that objects cannot be directly compared. Instead, compare their properties, eg.
if (obj1[i].Name == obj2[i].Name && obj1[i].age == obj2[i].age) {
// do something...
}
Well, as all the other answer already tell you, you'll have to compare the keys, not the objects themselves. Just adding another implementation
function isObject(value) {
return typeof value === "object" && value !== null;
}
function isSimilar(a, b) {
if (a === b) return true;
return isObject(a) && isObject(b) &&
Object.keys(a).every(key => a[key] === undefined || key in b && isSimilar(a[key], b[key])) &&
Object.keys(b).every(key => b[key] === undefined || key in a);
}
var obj1 = [{
Name: 'abc',
age: 23,
thisKeyIsIgnoredSinceItIsUndefined: undefined
}, {
Name: 'def',
age: 222
}];
var obj2 = [{
age: 23,
Name: 'abc'
}, {
age: 222,
Name: 'def'
}];
console.log(isSimilar(obj1, obj2))
well there is better way though examples above will work but they will fail for unknown keys.
function compareArrayObjects(ob1, ob2) {
let ob1Keys = Object.keys(ob1);
let ob2Keys = Object.keys(ob2);
return (ob1Keys.length == ob2Keys.length?(ob1Keys.every(key)=>(obj1[key]==ob2[key]))
:false);
}
or 1 line solution will be
_.intersection(obj1,obj2);

Comparing Arrays of Objects in JavaScript

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

Categories

Resources