1

I have a program that does a deep compare on 2 objects provided to it and uses recursion to do so. My problem is that since I am using a global variable to retain information, I have to reset it every time prior to making any subsequent calls to the function. Is there someway I can maintain the variable value other than using a global variable and have it be not so cumbersome?

let isEqual = true;
function deepEqual(object1, object2) {

  if (!((typeof(object1) == 'object' && typeof(object2) == 'object') || (object1 && object2))) {

    return isEqual = object1 === object2;

  } else if (typeof(object1) == 'object' && typeof(object2) == 'object') {

    if ((object1 && object2)) {

      let object1Keys = Object.keys(object1);

      let object2Keys = Object.keys(object2);
    
      if (object1Keys.length == object2Keys.length) {
        for (let index = 0; index < object1Keys.length; index++) {
          if (isEqual) {
            if (!(typeof(object1[object1Keys[index]]) == 'object' && typeof(object2[object2Keys[index]]) == 'object')) {
             isEqual = (object1[object1Keys[index]] === object2[object2Keys[index]]) && (object1Keys[index] === object2Keys[index]);
            } else {
              deepEqual(object1[object1Keys[index]], object2[object2Keys[index]]);
            }

          } else {
            return isEqual = false;
          }
        }
      }
    }

  }

  return isEqual;
}

let obj1 = {
  a: 'somestring',
  b: 42,
  c: {
    1: 'one',
    2: {
      4: 'Three'
    }
  }
};

let obj2 = {
  a: 'somestring',
  b: 42,
  c: {
    1: 'one',
    2: {
      3: 'Three'
    }
  }
};
console.log("obj1 == obj2 : ");
console.log(deepEqual(obj1, obj2));


let obj3 = {
  a: 'somestring',
  b: 42,
  c: {
    1: 'one',
    2: {
      3: 'Three'
    }
  }
};

let obj4 = {
  a: 'somestring',
  b: 42,
  c: {
    1: 'one',
    2: {
      3: 'Three'
    }
  }
};
console.log("obj3 == obj4 : ");
isEqual = true;
console.log(deepEqual(obj3, obj4));
let obj = {name: {gender: "F"}, age: 20};
isEqual = true;
console.log(deepEqual(obj, {name: {gender: "F"}, age: 20}));
  • JSON.stringify(obj1) === JSON.stringify(obj2) // JSON.stringify(obj3) === JSON.stringify(obj4) – Randy Casburn Nov 07 '18 at 03:31
  • @RandyCasburn Thanks! Is there any way to achieve it via a pure recursive function. I want to learn recursion or rather a good way to implement it. :) – glitterShimmer Nov 07 '18 at 03:36
  • [recursive diff](https://stackoverflow.com/a/33233053/633183) and [recursive union](https://stackoverflow.com/a/52977145/633183) - I think these two Q&As will show you other important things to consider. – Mulan Nov 08 '18 at 04:47
  • comparing the the length of keys is not reliable. `{ a: 1 }` and `{ b: 2 }` both have one key but that tells you nothing about equality. – Mulan Nov 08 '18 at 04:55
  • Are `{ a: /foo/ }` and `{ a: /foo/ }` considered equal? How about `{ a: someFunc }` and `{ a: someFunc }`? Consider other complex objects like [HTMLElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement) too. You have to ask and answer more questions before your object equality function can be robust and reliable. – Mulan Nov 08 '18 at 05:00
  • re: why I am using keys.length, its because if it immediately disqualifies an object from being equal to another if they dont have the same number of keys. The code checks later if the keys are the same, and same with values – glitterShimmer Nov 08 '18 at 23:56

4 Answers4

2

You don't need to use it at all: you can do the whole thing via recursion:

function deepEqual(o1, o2){
  if (typeof o1 != typeof o2)
    return false;

  if (typeof o1 != 'object' || o1 === null || o2 === null)
    return o1 === o2;

  for (var k in o1){
   if (!deepEqual(o1[k], o2[k]))
    return false;
  }
  for (var k in o2){
    if (!(k in o1))
      return false;
  }
  return true;
}
Andrew Ridgway
  • 574
  • 2
  • 9
0

I have created an utility just to deep compare two object. It uses the recursive call with two object and return true or false.

Github link for repo - https://github.com/maninder-singh/deep-compare

<script src="deep-compare.js"></script>

JS

    1. dc(null,null);
    2. dc("a","a");
    3. dc("a","ab");
    4. dc("a",undefined);
    5. dc(undefined,undefined);
    6. dc({},[]);
    7. dc({a:1},{});
    8. dc({a:1},{a:1});
    9. dc(true,true);
    10. dc(true,false);
front_end_dev
  • 1,998
  • 1
  • 9
  • 14
0

You can use tested, bullet proof Object equality methods provided my various JS library to perform Object equality testing as illustrated below

lodash Library:

_.isEqual(obj1, obj2)

Or
Custom Tested Method

function deepCompare () {
  var i, l, leftChain, rightChain;

  function compare2Objects (x, y) {
    var p;

    // remember that NaN === NaN returns false
    // and isNaN(undefined) returns true
    if (isNaN(x) && isNaN(y) && typeof x === 'number' && typeof y === 'number') {
         return true;
    }

    // Compare primitives and functions.     
    // Check if both arguments link to the same object.
    // Especially useful on the step where we compare prototypes
    if (x === y) {
        return true;
    }

    // Works in case when functions are created in constructor.
    // Comparing dates is a common scenario. Another built-ins?
    // We can even handle functions passed across iframes
    if ((typeof x === 'function' && typeof y === 'function') ||
       (x instanceof Date && y instanceof Date) ||
       (x instanceof RegExp && y instanceof RegExp) ||
       (x instanceof String && y instanceof String) ||
       (x instanceof Number && y instanceof Number)) {
        return x.toString() === y.toString();
    }

    // At last checking prototypes as good as we can
    if (!(x instanceof Object && y instanceof Object)) {
        return false;
    }

    if (x.isPrototypeOf(y) || y.isPrototypeOf(x)) {
        return false;
    }

    if (x.constructor !== y.constructor) {
        return false;
    }

    if (x.prototype !== y.prototype) {
        return false;
    }

    // Check for infinitive linking loops
    if (leftChain.indexOf(x) > -1 || rightChain.indexOf(y) > -1) {
         return false;
    }

    // Quick checking of one object being a subset of another.
    // todo: cache the structure of arguments[0] for performance
    for (p in y) {
        if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
            return false;
        }
        else if (typeof y[p] !== typeof x[p]) {
            return false;
        }
    }

    for (p in x) {
        if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
            return false;
        }
        else if (typeof y[p] !== typeof x[p]) {
            return false;
        }

        switch (typeof (x[p])) {
            case 'object':
            case 'function':

                leftChain.push(x);
                rightChain.push(y);

                if (!compare2Objects (x[p], y[p])) {
                    return false;
                }

                leftChain.pop();
                rightChain.pop();
                break;

            default:
                if (x[p] !== y[p]) {
                    return false;
                }
                break;
        }
    }

    return true;
  }

  if (arguments.length < 1) {
    return true; //Die silently? Don't know how to handle such case, please help...
    // throw "Need two or more arguments to compare";
  }

  for (i = 1, l = arguments.length; i < l; i++) {

      leftChain = []; //Todo: this can be cached
      rightChain = [];

      if (!compare2Objects(arguments[0], arguments[i])) {
          return false;
      }
  }

  return true;
}

Reference: Object comparison in JavaScript

Jagajit Prusty
  • 2,070
  • 2
  • 21
  • 39
0

function deepEqual(object1, object2) {
  //check if the given objects have the same datatype
  if (typeof(object1) === typeof(object2)) {
    //check if the given object is a primitive datatype and how to handle null values
    if ((typeof(object1) !== 'object') && (typeof(object2) !== 'object') ||
      object1 === null || object2 === null) {
      return object1 === object2;
    } else {
      //if they are both objects
      if (object1 !== null && object2 !== null) {
        let object1Keys = Object.keys(object1);
        let object2Keys = Object.keys(object2);

        //check if the arrays have the same length
        if (object1Keys.length === object2Keys.length) {
          let isEqual;
          for (let index = 0; index < object1Keys.length; index++) {
            //make sure both key:value pairs match
            if (object1Keys[index] === object2Keys[index]) {
              //check if the current value is another object
              if (typeof(object1[object1Keys[index]]) === 'object' &&
                typeof(object2[object2Keys[index]]) === 'object') {
                return deepEqual(object1[object1Keys[index]], object2[object2Keys[index]]);
              } else {

                isEqual = (object1[object1Keys[index]] === object2[object2Keys[index]]);
              }
            } else {

              return false; //return false if keys dont match
            }
          }
          return isEqual;

        } else {
          return false; //return false if 2 arrays dont have the same length
        }

      }
    }
  } else {
    return false; //return false if 2 object types dont match
  }
}

let obj1 = {
  a: 'somestring',
  b: 42,
  c: {
    1: 'one',
    2: {
      3: 'Three'
    }
  }
};

let obj2 = {
  a: 'somestring',
  b: 42,
  e: {
    1: 'one',
    2: {
      3: 'Three'
    }
  }
};
console.log("obj1 == obj2 : ");
console.log(deepEqual(obj1, obj2));


let obj3 = {
  a: 'somestring',
  b: 42,
  c: {
    1: 'one',
    2: {
      4: 'Three'
    }
  }
};

let obj4 = {
  a: 'somestring',
  b: 42,
  c: {
    1: 'one',
    2: {
      3: 'Three'
    }
  }
};
console.log("obj3 == obj4 : ");

console.log(deepEqual(obj3, obj4));
let obj = {
  name: {
    gender: "F"
  },
  age: 20
};

console.log(deepEqual(obj, {
  name: {
    gender: "F"
  },
  age: 20
}));
console.log('null == obj3');
console.log(deepEqual(null, obj3));
console.log('5 == obj3');
console.log(deepEqual(5, obj3));
console.log('null == null');
console.log(deepEqual(null, null));
console.log('10 == 5');
console.log(deepEqual(10, 5));
console.log(`10 == '10'`);
console.log(deepEqual(10, '10'));

In all honesty, I prefer @Andrew Ridgway's solution. Its very simple and elegant.

However, I did clean-up the function I was using to avoid using global variables.

This is another solution to same problem, though a bit complex.

I am open to further suggestions. Thanks!