Question

I need to compare two strings which represent json objects. For testing purposes I need a way to compare these strings ignoring not only the child elements order (which is quite common) but order of elements in array properties of jsons. I.e.:

group: {
    id: 123,
    users: [
       {id: 234, name: John},
       {id: 345, name: Mike}
    ]
}

should be equal to:

group: {
    id: 123,
    users: [
       {id: 345, name: Mike},
       {id: 234, name: John}
    ]
}

Ideally I need some javascript lib, but other approaches welcome too.

No correct solution

OTHER TIPS

Use JSONAssert

They have a loose assert.

Loose:

JSONAssert.assertEquals(exp, act, false);

Strict:

JSONAssert.assertEquals(exp, act, true);

I don't know if such thing exist, but you can implement it yourself.

var group1 = {
    id: 123,
    users: [
       {id: 234, name: "John"},
       {id: 345, name: "Mike"}
    ]
};

var group2 = {
    id: 123,
    users: [
       {id: 345, name: "Mike"},
       {id: 234, name: "John"}
    ]
};

function equal(a, b) {

    if (typeof a !== typeof b) return false;
    if (a.constructor !== b.constructor) return false;

    if (a instanceof Array)
    {
        return arrayEqual(a, b);
    }

    if(typeof a === "object")
    {
        return objectEqual(a, b);
    }

    return a === b;
}

function objectEqual(a, b) {
    for (var x in a)
    {
         if (a.hasOwnProperty(x))
         {
             if (!b.hasOwnProperty(x))
             {
                 return false;
             }

             if (!equal(a[x], b[x]))
             {
                 return false;
             }
         }
    }

    for (var x in b)
    {
        if (b.hasOwnProperty(x) && !a.hasOwnProperty(x))
        {
            return false;
        }
    }

    return true;
}

function arrayEqual(a, b) {
    if (a.length !== b.length)
    {
        return false;
    }

    var i = a.length;

    while (i--)
    {
        var j = b.length;
        var found = false;

        while (!found && j--)
        {
            if (equal(a[i], b[j])) found = true;
        }

        if (!found)
        {
            return false;
        }
    }

    return true;
}

alert(equal(group1, group2))

You could slice the arrays, sort them by Id then stringify them to JSON and compare the strings. For a lot of members it should work pretty fast. If you duplicate Ids, it will fail because sort will not change the order.

Here is my attempt at a custom implementation:

var equal = (function(){
  function isObject(o){
    return o !== null && typeof o === 'object';
  }
  return function(o1, o2){
    if(!isObject(o1) || !isObject(o2)) return o1 === o2;
    var key, allKeys = {};
    for(key in o1)
      if(o1.hasOwnProperty(key))
        allKeys[key] = key;
    for(key in o2)
      if(o2.hasOwnProperty(key))
        allKeys[key] = key;
    for(key in allKeys){
      if(!equal(o1[key], o2[key])) return false;
    }
    return true;
  }
})();

An example of it with test cases:

var p1 = {
  tags: ['one', 'two', 'three'],
  name: 'Frank',
  age: 24,
  address: {
    street: '111 E 222 W',
    city: 'Provo',
    state: 'Utah',
    zip: '84604'
  }
}
var p2 = {
  name: 'Frank',
  age: 24,
  tags: ['one', 'two', 'three'],
  address: {
    street: '111 E 222 W',
    city: 'Provo',
    state: 'Utah',
    zip: '84604'
  }
}
var p3 = {
  name: 'Amy',
  age: 24,
  tags: ['one', 'two', 'three'],
  address: {
    street: '111 E 222 W',
    city: 'Provo',
    state: 'Utah',
    zip: '84604'
  }
}
var p4 = {
  name: 'Frank',
  age: 24,
  tags: ['one', 'two', 'three'],
  address: {
    street: '111 E 222 W',
    city: 'Payson',
    state: 'Utah',
    zip: '84604'
  }
}
var p5 = {
  name: 'Frank',
  age: 24,
  tags: ['one', 'two'],
  address: {
    street: '111 E 222 W',
    city: 'Provo',
    state: 'Utah',
    zip: '84604'
  }
}

var equal = (function(){
  function isObject(o){
    return o !== null && typeof o === 'object';
  }
  return function(o1, o2){
    if(!isObject(o1) || !isObject(o2)) return o1 === o2;
    var key, allKeys = {};
    for(key in o1)
      if(o1.hasOwnProperty(key))
        allKeys[key] = key;
    for(key in o2)
      if(o2.hasOwnProperty(key))
        allKeys[key] = key;
    for(key in allKeys){
      if(!equal(o1[key], o2[key])) return false;
    }
    return true;
  }
})();

var cases = [
  {name: 'Compare with self', a: p1, b: p1, expected: true},
  {name: 'Compare with identical', a: p1, b: p2, expected: true},
  {name: 'Compare with different', a: p1, b: p3, expected: false},
  {name: 'Compare with different (nested)', a: p1, b: p4, expected: false},
  {name: 'Compare with different (nested array)', a: p1, b: p5, expected: false}
];

function runTests(tests){
  var outEl = document.getElementById('out');
  for(var i=0; i < tests.length; i++){
    var actual = equal(tests[i].a, tests[i].b),
        result = tests[i].expected == actual
          ? 'PASS'
          : 'FAIL';
    outEl.innerHTML += 
      '<div class="test ' + result + '">' + 
        result + ' ' +
        tests[i].name + 
      '</div>';
  }
}
runTests(cases);
body{
  font-family:monospace;
}
.test{
  margin:5px;
  padding:5px;  
}
.PASS{
  background:#EFE;
  border:solid 1px #32E132;
}
.FAIL{
  background:#FEE;  
  border:solid 1px #FF3232;
}
<div id=out></div>

I like the solution of Francis and it workes very well.

Just add the following null check at the beginning of the equal function to prevent errors with null or undefined inputs..

if (a == null && b == null) {
  return true;
}
if (a == null || b == null) {
  return false;
}

So the whole solution would look something like:

function equal(a, b) {
    if (a == null && b == null) {
      return true;
    }
    if (a == null || b == null) {
      return false;
    }
    if (typeof a !== typeof b) return false;
    if (a.constructor !== b.constructor) return false;

    if (a instanceof Array)
    {
        return arrayEqual(a, b);
    }

    if(typeof a === "object")
    {
        return objectEqual(a, b);
    }

    return a === b;
}

function objectEqual(a, b) {
    for (var x in a)
    {
         if (a.hasOwnProperty(x))
         {
             if (!b.hasOwnProperty(x))
             {
                 return false;
             }

             if (!equal(a[x], b[x]))
             {
                 return false;
             }
         }
    }

    for (var x in b)
    {
        if (b.hasOwnProperty(x) && !a.hasOwnProperty(x))
        {
            return false;
        }
    }

    return true;
}

function arrayEqual(a, b) {
    if (a.length !== b.length)
    {
        return false;
    }

    var i = a.length;

    while (i--)
    {
        var j = b.length;
        var found = false;

        while (!found && j--)
        {
            if (equal(a[i], b[j])) found = true;
        }

        if (!found)
        {
            return false;
        }
    }

    return true;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top