DEV Community

sanderdebr
sanderdebr

Posted on • Updated on

Deep Equality checking of Objects in Vanilla JavaScript 👨‍👦

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.

Top comments (7)

Collapse
 
sas_sam profile image
Sas Sam

Hi,

Thanks for the post, it's really helpful!

I've just reformatted and refactored a bit your code, so now it's following ESLint standards.

export function compareObjects(obj1, obj2) {
    if (obj1 === obj2) return true;

    if (
        typeof obj1 !== 'object' ||
        typeof obj2 !== 'object' ||
        obj1 == null ||
        obj2 == null
    ) {
        return false;
    }

    const keysA = Object.keys(obj1);
    const keysB = Object.keys(obj2);

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

    let result = true;

    keysA.forEach((key) => {
        if (!keysB.includes(key)) {
            result = false;
        }

        if (
            typeof obj1[key] === 'function' ||
            typeof obj2[key] === 'function'
        ) {
            if (obj1[key].toString() !== obj2[key].toString()) {
                result = false;
            }
        }

        if (!compareObjects(obj1[key], obj2[key])) {
            result = false;
        }
    });

    return result;
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
tjslater profile image
Thomas Slater

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

Collapse
 
sanderdebr profile image
sanderdebr • Edited

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

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 !

Collapse
 
amaslakov profile image
Aleksei Maslakov

What if you have objects with different field order? Like { foo: 'bar', baz: 42 } vs { baz: 42, foo: 'bar' }. How do you handle that with JSON.stringify?