views:

4672

answers:

9

What is the best way to compare Objects in JavaScript?

Example:

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

I know that "Two objects are equal if they refer to the exact same Object", but is there a way to check it another way??

Using this way works for me.....but is it the only possibility?

var eq = Object.toJSON(user1) == Object.toJSON(user2);
alert(eq); // gives true
+7  A: 

Certainly not the only way - you could prototype a method (against Object here but I certainly wouldn't suggest using Object for live code) to replicate C#/Java style comparison methods.

Edit, since a general example seems to be expected:

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 testing methods with toString() is absolutely not good enough but a method which would be acceptable is very hard because of the problem of whitespace having meaning or not, never mind synonym methods and methods producing the same result with different implementations. And the problems of prototyping against Object in general.

annakata
thx, and same question as above...does this work with object methods?
spankmaster79
Yes and no. It doesn't as written because the two methods will be different object instances (where not prototyped), but you could adapt this to a determine the methods are equivalent (though that's not straightforward).
annakata
You're only looping through "this" elements. Wouldn't your equals method return true when "this" is a subset of a larger "x"? Shouldn't you also add a for (p in x) loop?
Nosredna
The code is buggy. o1.equals(o2) returns true if all properties of o1 are equal to those of o2. But if o2 also has other properties that o1 does not have, the objects may be incorrectly marked as equal.
molf
Additionally, it would benefit from recursive comparison, using equals() internally to compare properties that are objects.
molf
m@olf, I just barely beat you to that complaint. :-)
Nosredna
the above was not intended as a general solution (though I made a rod for my own back using "Object", should be "MyObject") but for comparison between the two objects in the OP. Comparing methods is actually extremely difficult as meaningless whitespace variations are *very* hard to detect.
annakata
really struggling to understand the downvotes given the original question and my amends...
annakata
It works much better now with your fixes.
Nosredna
A: 

if you want to check for methods explicitly you can use the method.toSource() or method.toString() methods.

snz3
really not good enough for the reasons I described
annakata
So you'd spin through the elements of the object, and check what the types are, then use toSource() or toString() when you find a function?
Nosredna
Nosredna, yes. That would give you the actual text of the function.annakata, I don't understand what's not good enough and what you are actually trying to do. Could you elaborate a bit?
snz3
@snz3 - there's a serious problem with whitespace, dropped semi-colons and braces and similar syntax differences which may or may not have an impact, and are hard to determine without parsing i.e. decoupling from a raw string format. There's also the problem of fluctuating state and prototyping. Basically strings are not good enough at capturing the state of two objects.
annakata
+8  A: 

theres 2 more bug that will cause an error in the given answer

this is the correct algo:

Object.prototype.equals = function(x)
{

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

for(p in this)
{
    if (this[p])
    {
        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; }
        }
    }
    else
    {
        if (x[p])
        {
            return false;
        }
    }
}

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

return true;
}
crazyx
The check against `undefined` will fail for when a property is defined but set to the `undefined` value. Use the `in` operator instead of `typeof` to avoid this: `p in x`. Also comparing functions by string value is highly unreliable. Apart from the usual reasons that function decomposition fails, it's also very common to have two functions with the same code but very different behaviour due to closures. eg. any function created by jQuery's `$.proxy` or Prototype's `Function#bind`. I'd just stick with comparing function identity.
bobince
I think you should use *identical* comparison operator: `===`, cause `{ a: 5 }` and `{ a: "5.0" }` aren't equal, or are they?
Crozin
A: 

been searching for about 2 frustrating hours for something like this. 10x, your code worked great and stopped me from pulling out any more of my hair :)

andrei
This should be a comment, not an answer.
Gary Willoughby
A: 

if you work without the JSON-Lib, maybe this will help ya out:

Object.prototype.equals = function(b) {
 var a = this;
 for(i in a) {  
  if(typeof b[i] == 'undefined') {
   return false;
  }
  if(typeof b[i] == 'object') {
   if(!b[i].equals(a[i])) {
    return false;
   }
  }
  if(b[i] != a[i]) {
   return false;
  }
 }
 for(i in b) {
  if(typeof a[i] == 'undefined') {
   return false;
  }
  if(typeof a[i] == 'object') {
   if(!a[i].equals(b[i])) {
    return false;
   }
  }
  if(a[i] != b[i]) {
   return false;
  }
 } 
 return true;
}

var a = {foo:'bar', bar: {blub:'bla'}};
var b = {foo:'bar', bar: {blub:'blob'}};
alert(a.equals(b)); // alert's a false
Samuel Weber
A: 

We should probably add

if( typeof( this[p] ) != typeof( x[p] ) ) return false;

as the first statement of the first for loop in annakata's code.

Phillip
That would be better as a comment to their code.
graham.reeds
+1  A: 

I have modified a bit the code above. for me 0 !== false and null !== undefined. If you do not need such strict check remove one "=" sign in "this[p] !== x[p]" inside the code.

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

Then I have tested it with next objects:

var a = {a: 'text', b:[0,1]};
var b = {a: 'text', b:[0,1]};
var c = {a: 'text', b: 0};
var d = {a: 'text', b: false};
var e = {a: 'text', b:[1,0]};
var f = {a: 'text', b:[1,0], f: function(){ this.f = this.b; }};
var g = {a: 'text', b:[1,0], f: function(){ this.f = this.b; }};
var h = {a: 'text', b:[1,0], f: function(){ this.a = this.b; }};
var i = {
    a: 'text',
    c: {
        b: [1, 0],
        f: function(){
            this.a = this.b;
        }
    }
};
var j = {
    a: 'text',
    c: {
        b: [1, 0],
        f: function(){
            this.a = this.b;
        }
    }
};
var k = {a: 'text', b: null};
var l = {a: 'text', b: undefined};

a==b expected true; returned true

a==c expected false; returned false

c==d expected false; returned false

a==e expected false; returned false

f==g expected true; returned true

h==g expected false; returned false

i==j expected true; returned true

d==k expected false; returned false

k==l expected false; returned false

YoZik
A: 

Testing For Equality In JavaScript

QUnit is using slightly different version.

NV
A: 
mhoms