41

Possible Duplicate:
How do you determine equality for two JavaScript objects?
Object comparison in JavaScript

If I have two arrays or objects and want to compare them, such as

object1 = [
 { shoes:
   [ 'loafer', 'penny' ]
  },
  { beers:
     [ 'budweiser', 'busch' ]
  }
]

object2 = [
 { shoes:
   [ 'loafer', 'penny' ]
  },
  { beers:
     [ 'budweiser', 'busch' ]
  }
]

object1 == object2 // false

this can be annoying if you're getting a response from a server and trying to see if it's changed

1

2 Answers 2

39

Update:
In response to the comments and worries surrounding the original suggestion (comparing 2 JSON strings), you could use this function:

function compareObjects(o, p)
{
    var i,
        keysO = Object.keys(o).sort(),
        keysP = Object.keys(p).sort();
    if (keysO.length !== keysP.length)
        return false;//not the same nr of keys
    if (keysO.join('') !== keysP.join(''))
        return false;//different keys
    for (i=0;i<keysO.length;++i)
    {
        if (o[keysO[i]] instanceof Array)
        {
            if (!(p[keysO[i]] instanceof Array))
                return false;
            //if (compareObjects(o[keysO[i]], p[keysO[i]] === false) return false
            //would work, too, and perhaps is a better fit, still, this is easy, too
            if (p[keysO[i]].sort().join('') !== o[keysO[i]].sort().join(''))
                return false;
        }
        else if (o[keysO[i]] instanceof Date)
        {
            if (!(p[keysO[i]] instanceof Date))
                return false;
            if ((''+o[keysO[i]]) !== (''+p[keysO[i]]))
                return false;
        }
        else if (o[keysO[i]] instanceof Function)
        {
            if (!(p[keysO[i]] instanceof Function))
                return false;
            //ignore functions, or check them regardless?
        }
        else if (o[keysO[i]] instanceof Object)
        {
            if (!(p[keysO[i]] instanceof Object))
                return false;
            if (o[keysO[i]] === o)
            {//self reference?
                if (p[keysO[i]] !== p)
                    return false;
            }
            else if (compareObjects(o[keysO[i]], p[keysO[i]]) === false)
                return false;//WARNING: does not deal with circular refs other than ^^
        }
        if (o[keysO[i]] !== p[keysO[i]])//change !== to != for loose comparison
            return false;//not the same value
    }
    return true;
}

But in many cases, it needn't be that difficult IMO:

JSON.stringify(object1) === JSON.stringify(object2);

If the stringified objects are the same, their values are alike.
For completeness' sake: JSON simply ignores functions (well, removes them all together). It's meant to represent Data, not functionality.
Attempting to compare 2 objects that contain only functions will result in true:

JSON.stringify({foo: function(){return 1;}}) === JSON.stringify({foo: function(){ return -1;}});
//evaulutes to:
'{}' === '{}'
//is true, of course

For deep-comparison of objects/functions, you'll have to turn to libs or write your own function, and overcome the fact that JS objects are all references, so when comparing o1 === ob2 it'll only return true if both variables point to the same object...

As @a-j pointed out in the comment:

JSON.stringify({a: 1, b: 2}) === JSON.stringify({b: 2, a: 1});

is false, as both stringify calls yield "{"a":1,"b":2}" and "{"b":2,"a":1}" respectively. As to why this is, you need to understand the internals of chrome's V8 engine. I'm not an expert, and without going into too much detail, here's what it boils down to:

Each object that is created, and each time it is modified, V8 creates a new hidden C++ class (sort of). If object X has a property a, and another object has the same property, both these JS objects will reference a hidden class that inherits from a shared hidden class that defines this property a. If two objects all share the same basic properties, then they will all reference the same hidden classes, and JSON.stringify will work exactly the same on both objects. That's a given (More details on V8's internals here, if you're interested).

However, in the example pointed out by a-j, both objects are stringified differently. How come? Well, put simply, these objects never exist at the same time:

JSON.stringify({a: 1, b: 2})

This is a function call, an expression that needs to be resolved to the resulting value before it can be compared to the right-hand operand. The second object literal isn't on the table yet.
The object is stringified, and the exoression is resolved to a string constant. The object literal isn't being referenced anywhere and is flagged for garbage collection.
After this, the right hand operand (the JSON.stringify({b: 2, a: 1}) expression) gets the same treatment.

All fine and dandy, but what also needs to be taken into consideration is that JS engines now are far more sophisticated than they used to be. Again, I'm no V8 expert, but I think its plausible that a-j's snippet is being heavily optimized, in that the code is optimized to:

"{"b":2,"a":1}" === "{"a":1,"b":2}"

Essentially omitting the JSON.stringify calls all together, and just adding quotes in the right places. That is, after all, a lot more efficient.

Sign up to request clarification or add additional context in comments.

18 Comments

This isn't necessarily true. This doesn't encompass all cases, ie functions as a value, objects as values -- you may end up with lots of [Object Object] - which may be a false positive.
@ansiart: I never claimed this is a universal sollution. The OP wanted to compare two objects, like the ones in his question. To acchieve that, this answer is the easiest way.
I don't believe JSON.stringify has any guarantee of the ordering of the output. JSON.stringify({a: 1, b: 1}) could turn into '{"a": 1, "b": 1}' or '{"b": 1, "a": 1}' and the comparison will fail.
@pomo: Don't think that was intentional (It's a 4.5 year old answer). Probably forgot to call it recursively for array elements, or might have had something to do with the original question or a comment somewhere
@Michal: For someone taking the time to criticise a 5-year-old snippet of code, it's remarkable you can't work out what return true and return false means (yes, function returns boolean, true for equal, false otherwise). o and p are clearly the arguments which, given the function is called compareObjects, are probably objects (hence o). Either way: yeah, the name isn't great, the style isn't either... then again: it's 5 years old... if I were still doing JS, I would write it in a completely different way using ES6/ES2016, perhaps name it objectEquals or whatever
|
3

As an underscore mixin:

in coffee-script:

_.mixin deepEquals: (ar1, ar2) ->

    # typeofs should match
    return false unless (_.isArray(ar1) and _.isArray(ar2)) or (_.isObject(ar1) and _.isObject(ar2))

    #lengths should match
    return false if ar1.length != ar2.length

    still_matches = true

    _fail = -> still_matches = false

    _.each ar1, (prop1, n) =>

      prop2 = ar2[n]

      return if prop1 == prop2

      _fail() unless _.deepEquals prop1, prop2

    return still_matches

And in javascript:

_.mixin({
  deepEquals: function(ar1, ar2) {
    var still_matches, _fail,
      _this = this;
    if (!((_.isArray(ar1) && _.isArray(ar2)) || (_.isObject(ar1) && _.isObject(ar2)))) {
      return false;
    }
    if (ar1.length !== ar2.length) {
      return false;
    }
    still_matches = true;
    _fail = function() {
      still_matches = false;
    };
    _.each(ar1, function(prop1, n) {
      var prop2;
      prop2 = ar2[n];
      if (prop1 !== prop2 && !_.deepEquals(prop1, prop2)) {
        _fail();
      }
    });
    return still_matches;
  }
});

5 Comments

Funkodebat, the iterator function that you passed to underscore each has inconsistent return points: if (prop1 === prop2) { return; /* void */ } if (!_.deepEquals(prop1, prop2)) { return _fail(); /* false */ }
And what is the purpose of returning from the iterator? As I understood, _.each doesn't break the loop (if this is what you wanted)..
Or is it just a semi-automatic conversion from coffeescript, where every statement has a return value, so when calling "_fail() unless _.deepEquals prop1, prop2" it converts this to "return _fail()" ?
yea cofeescript just returns every last line of functions
Is there a difference to underscore's _.isEqual()? See underscorejs.org/#isEqual say it "Performs an optimized deep comparison between the two objects, to determine if they should be considered equal."

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.