Coder Perfect

[duplicate] Object comparison in JavaScript

Problem

In JavaScript, what is the best approach to compare objects?

Example:

var user1 = {name : "nerd", org: "dev"};
var user2 = {name : "nerd", org: "dev"};
var eq = user1 == user2;
alert(eq); // gives false

I understand that two objects are identical if they refer to the same object, but is there a way to see if their attribute values are the same?

The method below works for me, but is it the only option?

var eq = Object.toJSON(user1) == Object.toJSON(user2);
alert(eq); // gives true

Asked by spankmaster79

Solution #1

Unfortunately, there is no perfect solution until you recursively use _proto_ to access all non-enumerable properties, but this only works with Firefox.

So the best I can do is make educated guesses about possible usage cases.

When you have simple JSON-style objects with no functions and no DOM nodes within, this method works:

 JSON.stringify(obj1) === JSON.stringify(obj2) 

The ORDER OF THE PROPERTIES IS IMPORTANT, therefore for the following objects, this method will return false:

 x = {a: 1, b: 2};
 y = {b: 2, a: 1};

Compares objects without delving into prototypes, then recursively compares property projections and constructors.

This algorithm is almost correct:

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;
}

Known issues (well, they’re low priority, so you’re unlikely to see them):

Tests: the tests that are passed are from How can I tell whether two JavaScript objects are equal?

Answered by crazyx

Solution #2

Here’s my ES3 solution with comments (details after the code):

function object_equals( x, y ) {
  if ( x === y ) return true;
    // if both x and y are null or undefined and exactly the same

  if ( ! ( x instanceof Object ) || ! ( y instanceof Object ) ) return false;
    // if they are not strictly equal, they both need to be Objects

  if ( x.constructor !== y.constructor ) return false;
    // they must have the exact same prototype chain, the closest we can do is
    // test there constructor.

  for ( var p in x ) {
    if ( ! x.hasOwnProperty( p ) ) continue;
      // other properties were tested using x.constructor === y.constructor

    if ( ! y.hasOwnProperty( p ) ) return false;
      // allows to compare x[ p ] and y[ p ] when set to undefined

    if ( x[ p ] === y[ p ] ) continue;
      // if they have the same strict value or identity then they are equal

    if ( typeof( x[ p ] ) !== "object" ) return false;
      // Numbers, Strings, Functions, Booleans must be strictly equal

    if ( ! object_equals( x[ p ],  y[ p ] ) ) return false;
      // Objects and Arrays must be tested recursively
  }

  for ( p in y )
    if ( y.hasOwnProperty( p ) && ! x.hasOwnProperty( p ) )
      return false;
        // allows x[ p ] to be set to undefined

  return true;
}

In building this approach, I focused on corner cases and efficiency while still attempting to produce a basic solution that works, hopefully elegantly. Null and undefined attributes are allowed in JavaScript, and objects contain prototype chains that, if not verified, might result in drastically different behaviour.

First, I elected not to extend Object.prototype, primarily because null could not be one of the comparison objects, and I believe that null should be a viable comparison object. Others have raised reasonable concerns about the extension of Object.prototype, such as possible side effects on other people’s code.

There must be extra caution when dealing with the fact that JavaScript allows object properties to be set to undefined, i.e. there are properties with undefined values. To report equality, the preceding approach ensures that both objects have the identical properties set to undefined. This can only be done by using Object.hasOwnProperty(property name) to check for the existence of properties. Also, because JSON.stringify() eliminates properties with the value undefined, comparisons made using this form will ignore values with the value undefined.

Functions should only be deemed equal if they share the same reference, not merely the same code, because this ignores the prototype of these functions. As a result, comparing the code string will not ensure that they have the same prototype object.

Not simply the same characteristics, but the same prototype chain should be shared by the two objects. Only by comparing the constructors of both objects for rigorous equality can this be tested cross-browser. Using Object.getPrototypeOf in ECMAScript 5, they may test their actual prototype (). The __proto__ attribute in several web browsers accomplishes the same thing. When one of these techniques is available, a possible change to the above code would allow it to be used.

Because 2 should not be regarded equivalent to “2.0000,” and false should not be considered equal to null, undefined, or 0, stringent comparisons are essential.

Due to time constraints, I compare properties for equality as soon as possible. If it doesn’t work, look for the type of these properties. On huge objects with a lot of scalar attributes, the speed gain could be significant.

To catch these properties that are defined with an undefined value, just two loops are required: the first to check properties from the left object, and the second to examine properties from the right object and verify only existence (not value).

In total, this technique takes only 16 lines of code to handle most corner circumstances (without comments).

Updated on August 13, 2015. I have implemented a better version, as the function value_equals() that is faster, handles properly corner cases such as NaN and 0 different than -0, optionally enforcing objects’ properties order and testing for cyclic references, backed by more than 100 automated tests as part of the Toubkal project test suite.

Answered by Jean Vincent

Solution #3

  Utils.compareObjects = function(o1, o2){
    for(var p in o1){
        if(o1.hasOwnProperty(p)){
            if(o1[p] !== o2[p]){
                return false;
            }
        }
    }
    for(var p in o2){
        if(o2.hasOwnProperty(p)){
            if(o1[p] !== o2[p]){
                return false;
            }
        }
    }
    return true;
};

A simple method for comparing items that are only on one level.

Answered by pincopallo

Solution #4

Not the only option: you could prototype a method to mimic C#/Java style comparison methods (against Object here, although I wouldn’t recommend using Object for live code).

Since a broad example appears to be anticipated, I’ll make an edit:

Object.prototype.equals = function(x)
{
    for(p in this)
    {
        switch(typeof(this[p]))
        {
            case 'object':
                if (!this[p].equals(x[p])) { return false }; break;
            case 'function':
                if (typeof(x[p])=='undefined' || (p != 'equals' && this[p].toString() != x[p].toString())) { return false; }; break;
            default:
                if (this[p] != x[p]) { return false; }
        }
    }

    for(p in x)
    {
        if(typeof(this[p])=='undefined') {return false;}
    }

    return true;
}

Note that evaluating methods with toString() is insufficient; finding a method that is acceptable is difficult due to the issue of whether whitespace has meaning or not, let alone synonym methods and methods that produce the same result but with different implementations. In addition, there are issues with prototyping against Object in general.

Answered by annakata

Solution #5

Self-referential data structures, numbers, texts, dates, and, of course, plain nested javascript objects will all be handled by the following algorithm:

When two objects are regarded comparable,

The function text does not regard functions to be identical. Because functions may have different closures, this test is insufficient. Only if === says so are functions regarded equal (but you could easily extend that equivalent relation should you choose to do so).

Infinite loops are avoided, which might be created by circular datastructures. When areEquivalent tries to disprove equality by recursing into an object’s properties, it keeps note of which objects this sub-comparison is required for. If equality can be disproved, then some reachable property path differs between the objects, and the shortest such reachable path cannot contain cycles present in both paths; i.e. it is OK to presume equality while recursively comparing objects. The assumption is stored in the property areEquivalent Eq 91 2 34, which is removed after usage, but behavior is uncertain if the object graph already has such a property. Because javascript does not permit dictionaries with arbitrary names, the use of such a marker attribute is required.

function unwrapStringOrNumber(obj) {
    return (obj instanceof Number || obj instanceof String 
            ? obj.valueOf() 
            : obj);
}
function areEquivalent(a, b) {
    a = unwrapStringOrNumber(a);
    b = unwrapStringOrNumber(b);
    if (a === b) return true; //e.g. a and b both null
    if (a === null || b === null || typeof (a) !== typeof (b)) return false;
    if (a instanceof Date) 
        return b instanceof Date && a.valueOf() === b.valueOf();
    if (typeof (a) !== "object") 
        return a == b; //for boolean, number, string, xml

    var newA = (a.areEquivalent_Eq_91_2_34 === undefined),
        newB = (b.areEquivalent_Eq_91_2_34 === undefined);
    try {
        if (newA) a.areEquivalent_Eq_91_2_34 = [];
        else if (a.areEquivalent_Eq_91_2_34.some(
            function (other) { return other === b; })) return true;
        if (newB) b.areEquivalent_Eq_91_2_34 = [];
        else if (b.areEquivalent_Eq_91_2_34.some(
            function (other) { return other === a; })) return true;
        a.areEquivalent_Eq_91_2_34.push(b);
        b.areEquivalent_Eq_91_2_34.push(a);

        var tmp = {};
        for (var prop in a) 
            if(prop != "areEquivalent_Eq_91_2_34") 
                tmp[prop] = null;
        for (var prop in b) 
            if (prop != "areEquivalent_Eq_91_2_34") 
                tmp[prop] = null;

        for (var prop in tmp) 
            if (!areEquivalent(a[prop], b[prop]))
                return false;
        return true;
    } finally {
        if (newA) delete a.areEquivalent_Eq_91_2_34;
        if (newB) delete b.areEquivalent_Eq_91_2_34;
    }
}

Answered by Eamon Nerbonne

Post is based on https://stackoverflow.com/questions/1068834/object-comparison-in-javascript