如何比较两个jsons忽略数组属性中元素的顺序?

时间:2011-11-11 19:26:57

标签: javascript json compare equals jsonassert

我需要比较两个代表json对象的字符串。出于测试目的,我需要一种方法来比较这些字符串,而不仅忽略子元素顺序(这很常见),而忽略了jsons数组属性中元素的顺序。即:

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

应该等于:

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

理想情况下,我需要一些javascript lib,但也欢迎其他方法。

6 个答案:

答案 0 :(得分:5)

使用JSONAssert

他们有一个松散的断言。

松散:

JSONAssert.assertEquals(exp, act, false);

严格的:

JSONAssert.assertEquals(exp, act, true);

答案 1 :(得分:1)

您可以对数组进行切片,按Id对它们进行排序,然后将它们串化为JSON并比较字符串。对于很多成员来说,它应该非常快。如果您复制ID,它将失败,因为sort不会改变顺序。

答案 2 :(得分:0)

我不知道这样的事情是否存在,但你可以自己实现它。

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))

答案 3 :(得分:0)

以下是我对自定义实施的尝试:

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 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);
&#13;
body{
  font-family:monospace;
}
.test{
  margin:5px;
  padding:5px;  
}
.PASS{
  background:#EFE;
  border:solid 1px #32E132;
}
.FAIL{
  background:#FEE;  
  border:solid 1px #FF3232;
}
&#13;
<div id=out></div>
&#13;
&#13;
&#13;

答案 4 :(得分:0)

我喜欢弗朗西斯的解决方案,并且效果很好。

只需在equal函数的开头添加以下空检查,以防止输入为空或未定义的错误。

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

因此整个解决方案如下所示:

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

答案 5 :(得分:0)

此答案描述了使用 DeltaJSON REST API 解决问题的解决方案。 DeltaJSON 是一种商业产品,可将 API 作为服务 (SaaS) 或通过可在本地运行的 REST 服务器提供:

  1. 启动 DeltaJSON Rest Server(需要安装 Java 和许可证文件):
java -jar deltajson-rest-1.1.0.jar
  1. 在您的 JavaScript 中,调用 DeltaJSON REST API,并将 arrayAlignment 属性设置为 orderless

下面的示例代码显示了如何使用此属性设置调用 API:

async function runTest() {
  const group1 = {
    id: 123,
    users: [
      { id: 234, name: "John" },
      { id: 345, name: "Mike" }
    ]
  };
  const group2 = {
    id: 123,
    users: [
      { id: 345, name: "Mikey" },
      { id: 234, name: "John" }
    ]
  };

  // call wrapper function that makes the REST API call:
  const isEqual = await compare(group1, group2);
  // log the comparison result: true
  console.log("isEqual", isEqual);
}

async function compare(aData, bData) {
  const aString = JSON.stringify(aData);
  const bString = JSON.stringify(bData);
  const blobOptions = { type: "application/json" };

  var formdata = new FormData();
  formdata.append("a", new Blob([aString], blobOptions));
  formdata.append("b", new Blob([bString], blobOptions));
  formdata.append("arrayAlignment", "orderless");

  const myHeaders = new Headers();
  myHeaders.append("Accept", "application/json");

  var requestOptions = {
    method: "POST",
    headers: myHeaders,
    body: formdata,
    redirect: "follow"
  };

  try {
    const response = await fetch(
      "http://localhost:8080/api/json/v1/compare",
      requestOptions
    );
    const jsonObj = await response.json();
    console.log(jsonObj);
    const dataSets = jsonObj.dx_deltaJSON.dx_data_sets;
    const isEqual = dataSets === "A=B";
    return isEqual;
  } catch (e) {
    console.error(e);
  }
}

// run the test:
runTest(); // true

说明:

DeltaJSON Rest API 响应是 JSON 输入的注释形式。添加了额外的 dx_ 前缀属性来描述更改。 JSON 中还包含元数据 dx_deltaJSON 属性。

dx_deltaJSON 属性的值是一个具有 dx_data_sets 属性的对象,我们可以测试该属性以查看(在双向比较中)该值为 A=B。< /p>

这里的结果与问题中的输入略有不同。在这里,以及更改数组项的顺序,'Mike' 已更改为 'Mikey':

{
  "dx_deltaJSON": {
    "dx_data_sets": "A!=B",
    "dx_deltaJSON_type": "diff",
    "dx_deltaJSON_metadata": {
      "operation": {
        "type": "compare",
        "input-format": "multi_part",
        "output-format": "JSON"
      },
      "parameters": {
        "dxConfig": [],
        "arrayAlignment": "orderless",
        "wordByWord": false
      }
    },
    "dx_deltaJSON_delta": {
      "id": 123,
      "users": [
        {
          "id": 345,
          "name": {
            "dx_delta": {
              "A": "Mike",
              "B": "Mikey"
            }
          }
        },
        {
          "id": 234,
          "name": "John"
        }
      ]
    }
  }
}