DEV Community

loading...

Deep Equality checking of Objects in Vanilla JavaScript 👨‍đŸ‘Ļ

sanderdebr profile image sanderdebr ãƒģUpdated on ãƒģ3 min read

Did you ever found yourself in a situation where you needed to compare two objects to each other with JavaScript? Perhaps you then found out that JavaScript does not offer a native solution for this issue. In this tutorial we will build own implementation for this!

You will learn something about:

  • Pass by value vs pass by reference
  • Object.keys() method
  • Creating a recursive function

You could grab the Lodash library and use their .isEqual method to do a deep quality check of two objects but it is good practise to create solutions ourselves to practise vanilla JavaScript.

Let's say we have the following objects:

const obj1 = { name: 'Peter', stats: { points: 45, isActive: false }};
const obj2 = { name: 'Peter', stats: { points: 45, isActive: false }};

console.log(obj1 === obj2) // returns false

These two objects are exactly the same, still JavaScript returns false. Why?

This is because in JavaScript Primitives like strings and numbers are compared by their value. Objects on the other hand are compared by reference.

JavaScript assigns each object you create to its own place in memory. So even if you're objects have exactly the same content, their reference (place in memory) is different!

visual

Let's start creating our function. We will set up a function called compareObjects that takes in two arguments. First we'll check if the two arguments are of the same type and contain the same value.

const compareObjects = (a, b) => a === b ? true : false;

const obj1 = { name: 'Peter', stats: { points: 45, isActive: false }};

compareObjects(obj1, obj1) // returns true

Next up we'll add the check if the two arguments are actually of the type object and also are not null values. We want to avoid type conversion so we'll use != instead of !==:

const compareObjects = (a, b) => {
 if (a === b) return true;
​
 if (typeof a != 'object' || typeof b != 'object' || typeof a == null || typeof b == null) return false;
}

Then we'll check the length of the object keys of both objects. If they are not of the same length, we are sure the object are not the same.

...
let keysA = Object.keys(a), keysB = Object.keys(b);
 if (keysA.length != keysB.length) return false;
...

Next up we'll loop over the keys of the keysA array with an for of loop. Use for of for arrays and for in for objects.

Inside this loop, we'll check if every key exists inside the keysB array. Next to that, we'll compare the values of every key by passing them back into our compareObjects function, making our function recursive (calling itself).

As soon as one of our keys of values is not the same, it will stop the loop and the function and return false.

...
for (let key of keysA) {
    if (!keysB.includes(key) || !compareObjects(a[key], b[key])) return false;
}
...

We also want to check if the methods are the same, we will do this by converting the function to a string on comparing the two values:

...
if (typeof a[key] === 'function' || typeof b[key] === 'function') {
   if (a[key].toString() != b[key].toString()) return false;
}
...

If the loop checked every key and passed every nested value back into its own function and none returned false, there is only one thing left to do: return true!

The complete function:

const compareObjects = (a, b) => {
 if (a === b) return true;

 if (typeof a != 'object' || typeof b != 'object' || a == null || b == null) return false;

 let keysA = Object.keys(a), keysB = Object.keys(b);

 if (keysA.length != keysB.length) return false;

 for (let key of keysA) {
   if (!keysB.includes(key)) return false;

   if (typeof a[key] === 'function' || typeof b[key] === 'function') {
     if (a[key].toString() != b[key].toString()) return false;
   } else {
     if (!compareObjects(a[key], b[key])) return false;
   }
 }

 return true;
}

Thanks for following this tutorial, make sure to follow me for more! 🧠

Please see the book Eloquent JavaScript for further reference.

Discussion

pic
Editor guide
Collapse
tjslater profile image
Thomas Slater

Any particular reason why JSON.stringify isn't mentioned here?

Collapse
sanderdebr profile image
sanderdebr Author

In most cases it is pretty useful to use JSON methods, but functions and also React componens won't survive the serialization process using the JSON methods. But still, using the spread method for simple objects and JSON methods for more complex would be fine! My goal was to learn something about recursive functions with this article.

Relevant interesting article: dev.to/bytebodger/cloning-objects-...

Collapse
ansmtz profile image
ansmtz

Doesn't work with methods :(

Collapse
sanderdebr profile image
sanderdebr Author

You are right! I've updated the code to also check for methods:

if (typeof a[key] === 'function' || typeof b[key] === 'function') {
if (a[key].toString() != b[key].toString()) return false;
}

Thankyou!

Collapse
mohnnadbahaa profile image
Mohannad Bahaa

i think you can use JSON.stringify the both objects then you can compare it !
the only issue here you need to catch JSON.stringify error !