测试两个JSON对象是否相等,忽略Java中的子顺序

时间:2010-02-12 17:30:00

标签: java json junit

我正在寻找一个JSON解析库,它支持比较两个忽略子命令的JSON对象,特别是用于测试从Web服务返回的JSON的单元。

任何主要的JSON库都支持这个吗? org.json库只是进行参考比较。

29 个答案:

答案 0 :(得分:134)

尝试Skyscreamer的JSONAssert

非严格模式有两个主要优点,使其不那么脆弱:

  • 对象可扩展性(例如,期望值 {id:1} ,这仍然会通过: {id:1,moredata:'x'} 。)
  • 松散的数组排序(例如['dog','cat'] == ['cat','dog'])

在严格模式下,它的行为更像json-lib的测试类。

测试看起来像这样:

@Test
public void testGetFriends() {
    JSONObject data = getRESTData("/friends/367.json");
    String expected = "{friends:[{id:123,name:\"Corby Page\"}"
        + ",{id:456,name:\"Solomon Duskis\"}]}";
    JSONAssert.assertEquals(expected, data, false);
}

JSONAssert.assertEquals()调用中的参数是 expectedJSONString actualDataString isStrict

结果消息非常清楚,这在比较非常大的JSON对象时非常重要。

答案 1 :(得分:73)

作为一般的架构点,我通常建议不要让特定序列化格式的依赖性超出存储/网络层;因此,我首先建议您考虑测试您自己的应用程序对象之间的相等性,而不是它们的JSON表现形式。

话虽如此,我现在是Jackson的忠实粉丝,我快速阅读他们的ObjectNode.equals()实施建议您做出所需的会员资格比较:

public boolean equals(Object o)
{
    if (o == this) return true;
    if (o == null) return false;
    if (o.getClass() != getClass()) {
        return false;
    }
    ObjectNode other = (ObjectNode) o;
    if (other.size() != size()) {
        return false;
    }
    if (_children != null) {
        for (Map.Entry<String, JsonNode> en : _children.entrySet()) {
            String key = en.getKey();
            JsonNode value = en.getValue();

            JsonNode otherValue = other.get(key);

            if (otherValue == null || !otherValue.equals(value)) {
                return false;
            }
        }
    }
    return true;
}

答案 2 :(得分:40)

使用GSON

JsonParser parser = new JsonParser();
JsonElement o1 = parser.parse("{a : {a : 2}, b : 2}");
JsonElement o2 = parser.parse("{b : 2, a : {a : 2}}");
assertEquals(o1, o2);

答案 3 :(得分:23)

我会做以下事情,

JSONObject obj1 = /*json*/;
JSONObject obj2 = /*json*/;

ObjectMapper mapper = new ObjectMapper();

JsonNode tree1 = mapper.readTree(obj1.toString());
JsonNode tree2 = mapper.readTree(obj2.toString());

return tree1.equals(tree2);

答案 4 :(得分:13)

您可以尝试使用json-lib的JSONAssert类:

JSONAssert.assertEquals(
  "{foo: 'bar', baz: 'qux'}",
  JSONObject.fromObject("{foo: 'bar', baz: 'xyzzy'}")
);

给出:

junit.framework.ComparisonFailure: objects differed at key [baz]; expected:<[qux]> but was:<[xyzzy]>

答案 5 :(得分:12)

使用此库:https://github.com/lukas-krecan/JsonUnit

波姆:

<dependency>
    <groupId>net.javacrumbs.json-unit</groupId>
    <artifactId>json-unit</artifactId>
    <version>1.5.0</version>
    <scope>test</scope>
</dependency>

IGNORING_ARRAY_ORDER - 忽略数组中的顺序

assertJsonEquals("{\"test\":[1,2,3]}",
  "{\"test\":  [3,2,1]}",
  when(IGNORING_ARRAY_ORDER)
);

答案 6 :(得分:11)

如果您已经在使用JUnit,最新版本现在使用Hamcrest。它是一个通用匹配框架(特别适用于单元测试),可以扩展为构建新的匹配器。

有一个名为hamcrest-json的小型开源库,具有JSON感知匹配。它有详细记录,测试和支持。以下是一些有用的链接:

使用JSON库org.json.simple中的对象的示例代码:

Assert.assertThat(
    jsonObject1.toJSONString(),
    SameJSONAs.sameJSONAs(jsonObject2.toJSONString()));

可选地,您可以(1)允许“任意顺序”数组和(2)忽略额外字段。

由于Java有各种JSON库(JacksonGSONjson-lib等),因此hamcrest-json支持JSON文本(如java.lang.String),以及Douglas Crockford的JSON库org.json中原生支持的对象。

最后,如果您不使用JUnit,可以直接使用Hamcrest进行断言。 (I wrote about it here.

答案 7 :(得分:11)

您可以尝试JsonUnit。它可以比较两个JSON对象并报告差异。它建立在杰克逊之上。

例如

assertJsonEquals("{\"test\":1}", "{\n\"test\": 2\n}");

结果

java.lang.AssertionError: JSON documents are different:
Different value found in node "test". Expected 1, got 2.

答案 8 :(得分:6)

我做过的一件事就是将两个对象都读入HashMap,然后与常规的assertEquals()进行比较。它将调用hashmaps的equals()方法,它将递归地比较内部的所有对象(它们将是其他哈希映射或某个单值对象,如字符串或整数)。这是使用Codehaus的Jackson JSON解析器完成的。

assertEquals(mapper.readValue(expectedJson, new TypeReference<HashMap<String, Object>>(){}), mapper.readValue(actualJson, new TypeReference<HashMap<String, Object>>(){}));

如果JSON对象是一个数组,则可以使用类似的方法。

答案 9 :(得分:4)

对于org.json我推出了自己的解决方案,这是一种与JSONObject实例进行比较的方法。我没有在该项目中使用复杂的JSON对象,所以我不知道这是否适用于所有场景。另外,鉴于我在单元测试中使用它,我没有付出精力进行优化。这是:

public static boolean jsonObjsAreEqual (JSONObject js1, JSONObject js2) throws JSONException {
    if (js1 == null || js2 == null) {
        return (js1 == js2);
    }

    List<String> l1 =  Arrays.asList(JSONObject.getNames(js1));
    Collections.sort(l1);
    List<String> l2 =  Arrays.asList(JSONObject.getNames(js2));
    Collections.sort(l2);
    if (!l1.equals(l2)) {
        return false;
    }
    for (String key : l1) {
        Object val1 = js1.get(key);
        Object val2 = js2.get(key);
        if (val1 instanceof JSONObject) {
            if (!(val2 instanceof JSONObject)) {
                return false;
            }
            if (!jsonObjsAreEqual((JSONObject)val1, (JSONObject)val2)) {
                return false;
            }
        }

        if (val1 == null) {
            if (val2 != null) {
                return false;
            }
        }  else if (!val1.equals(val2)) {
            return false;
        }
    }
    return true;
}

答案 10 :(得分:4)

我正在使用它,对我来说效果很好(使用org.json。*):

package com.project1.helpers;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class JSONUtils {

    public static boolean areEqual(Object ob1, Object ob2) throws JSONException {
        Object obj1Converted = convertJsonElement(ob1);
        Object obj2Converted = convertJsonElement(ob2);
        return obj1Converted.equals(obj2Converted);
    }

    private static Object convertJsonElement(Object elem) throws JSONException {
        if (elem instanceof JSONObject) {
            JSONObject obj = (JSONObject) elem;
            Iterator<String> keys = obj.keys();
            Map<String, Object> jsonMap = new HashMap<>();
            while (keys.hasNext()) {
                String key = keys.next();
                jsonMap.put(key, convertJsonElement(obj.get(key)));
            }
            return jsonMap;
        } else if (elem instanceof JSONArray) {
            JSONArray arr = (JSONArray) elem;
            Set<Object> jsonSet = new HashSet<>();
            for (int i = 0; i < arr.length(); i++) {
                jsonSet.add(convertJsonElement(arr.get(i)));
            }
            return jsonSet;
        } else {
            return elem;
        }
    }
}

答案 11 :(得分:2)

试试这个:

public static boolean jsonsEqual(Object obj1, Object obj2) throws JSONException

    {
        if (!obj1.getClass().equals(obj2.getClass()))
        {
            return false;
        }

        if (obj1 instanceof JSONObject)
        {
            JSONObject jsonObj1 = (JSONObject) obj1;

            JSONObject jsonObj2 = (JSONObject) obj2;

            String[] names = JSONObject.getNames(jsonObj1);
            String[] names2 = JSONObject.getNames(jsonObj1);
            if (names.length != names2.length)
            {
                return false;
            }

            for (String fieldName:names)
            {
                Object obj1FieldValue = jsonObj1.get(fieldName);

                Object obj2FieldValue = jsonObj2.get(fieldName);

                if (!jsonsEqual(obj1FieldValue, obj2FieldValue))
                {
                    return false;
                }
            }
        }
        else if (obj1 instanceof JSONArray)
        {
            JSONArray obj1Array = (JSONArray) obj1;
            JSONArray obj2Array = (JSONArray) obj2;

            if (obj1Array.length() != obj2Array.length())
            {
                return false;
            }

            for (int i = 0; i < obj1Array.length(); i++)
            {
                boolean matchFound = false;

                for (int j = 0; j < obj2Array.length(); j++)
                {
                    if (jsonsEqual(obj1Array.get(i), obj2Array.get(j)))
                    {
                        matchFound = true;
                        break;
                    }
                }

                if (!matchFound)
                {
                    return false;
                }
            }
        }
        else
        {
            if (!obj1.equals(obj2))
            {
                return false;
            }
        }

        return true;
    }

答案 12 :(得分:2)

我将在http://json.org/java/处获取库,并修改JSONObject和JSONArray的equals方法以进行深度相等测试。为了确保它无法按照孩子的顺序工作,您需要做的就是用TreeMap替换内部地图,或者使用类似Collections.sort()的内容。

答案 13 :(得分:2)

Karate正是您所寻找的。这是一个例子:

* def myJson = { foo: 'world', hey: 'ho', zee: [5], cat: { name: 'Billie' } }
* match myJson = { cat: { name: 'Billie' }, hey: 'ho', foo: 'world', zee: [5] }

(免责声明:dev这里)

答案 14 :(得分:1)

这可能对那些使用 Spring Framework 的人有所帮助。您可以重用内部用于对 ResultActions 进行断言的内容(用于控制器测试):

导入:org.springframework.test.util.JsonExpectationsHelper

并且您可以编写因详细输出而中断的测试:

java.lang.AssertionError: someObject.someArray[1].someInternalObject2.value
Expected: 456
     got: 4567

测试代码:

@Test
void test() throws Exception {

    final String json1 =
        "{" +
        "  'someObject': {" +
        "    'someArray': [" +
        "      {" +
        "        'someInternalObject': {" +
        "          'value': '123'" +
        "        }" +
        "      }," +
        "      {" +
        "        'someInternalObject2': {" +
        "          'value': '456'" +
        "        }" +
        "      }" +
        "    ]" +
        "  }" +
        "}";

    final String json2 =
        "{" +
        "  'someObject': {" +
        "    'someArray': [" +
        "      {" +
        "        'someInternalObject': {" +
        "          'value': '123'" +
        "        }" +
        "      }," +
        "      {" +
        "        'someInternalObject2': {" +
        "          'value': '4567'" +
        "        }" +
        "      }" +
        "    ]" +
        "  }" +
        "}";

    new JsonExpectationsHelper().assertJsonEqual(json1, json2, true);
}

答案 15 :(得分:1)

您可以使用zjsonpatch库,它根据RFC 6902(JSON Patch)显示diff信息。它非常易于使用。请访问其描述页面了解其用途

答案 16 :(得分:1)

任何主要的JSON库都支持吗? org.json库仅进行参考比较。

但是org.json 确实支持此功能!使用similar()代替equals()

答案 17 :(得分:1)

为了比较jsons,我建议使用JSONCompare: https://github.com/fslev/json-compare

// Compare by regex
String expected = "{\"a\":\".*me.*\"}";
String actual = "{\"a\":\"some text\"}";
JSONCompare.assertEquals(expected, actual);  // True

// Check expected array has no extra elements
String expected = "[1,\"test\",4,\"!.*\"]";
String actual = "[4,1,\"test\"]";
JSONCompare.assertEquals(expected, actual);  // True

// Check expected array has no numbers
String expected = "[\"\\\\\\d+\"]";
String actual = "[\"text\",\"test\"]";
JSONCompare.assertEquals(expected, actual);  // True

// Check expected array has no numbers
String expected = "[\"\\\\\\d+\"]";
String actual = "[2018]";
JSONCompare.assertNotEquals(expected, actual);  // True

答案 18 :(得分:1)

我知道它通常仅用于测试,但您可以在Hamcrest JSON中使用Hamcrest JSON comparitorSameJSONAs

Hamcrest JSON SameJSONAs

答案 19 :(得分:0)

似乎没有其他工作做得很好,所以我写了这个:

private boolean jsonEquals(JsonNode actualJson, JsonNode expectJson) {
    if(actualJson.getNodeType() != expectJson.getNodeType()) return false;

    switch(expectJson.getNodeType()) {
    case NUMBER:
        return actualJson.asDouble() == expectJson.asDouble();
    case STRING:
    case BOOLEAN:
        return actualJson.asText().equals(expectJson.asText());
    case OBJECT:
        if(actualJson.size() != expectJson.size()) return false;

        Iterator<String> fieldIterator = actualJson.fieldNames();
        while(fieldIterator.hasNext()) {
            String fieldName = fieldIterator.next();
            if(!jsonEquals(actualJson.get(fieldName), expectJson.get(fieldName))) {
                return false;
            }
        }
        break;
    case ARRAY:
        if(actualJson.size() != expectJson.size()) return false;
        List<JsonNode> remaining = new ArrayList<>();
        expectJson.forEach(remaining::add);
        // O(N^2)   
        for(int i=0; i < actualJson.size(); ++i) {
            boolean oneEquals = false;
            for(int j=0; j < remaining.size(); ++j) {
                if(jsonEquals(actualJson.get(i), remaining.get(j))) {
                    oneEquals = true;
                    remaining.remove(j);
                    break;
                }
            }
            if(!oneEquals) return false;
        }
        break;
    default:
        throw new IllegalStateException();
    }
    return true;
}

答案 20 :(得分:0)

以下代码将更有助于比较两个JsonObject,JsonArray,JsonPrimitive和JasonElements。

private boolean compareJson(JsonElement json1, JsonElement json2) {
        boolean isEqual = true;
        // Check whether both jsonElement are not null
        if (json1 != null && json2 != null) {

            // Check whether both jsonElement are objects
            if (json1.isJsonObject() && json2.isJsonObject()) {
                Set<Entry<String, JsonElement>> ens1 = ((JsonObject) json1).entrySet();
                Set<Entry<String, JsonElement>> ens2 = ((JsonObject) json2).entrySet();
                JsonObject json2obj = (JsonObject) json2;
                if (ens1 != null && ens2 != null) {
                    // (ens2.size() == ens1.size())
                    // Iterate JSON Elements with Key values
                    for (Entry<String, JsonElement> en : ens1) {
                        isEqual = isEqual && compareJson(en.getValue(), json2obj.get(en.getKey()));
                    }
                } else {
                    return false;
                }
            }

            // Check whether both jsonElement are arrays
            else if (json1.isJsonArray() && json2.isJsonArray()) {
                JsonArray jarr1 = json1.getAsJsonArray();
                JsonArray jarr2 = json2.getAsJsonArray();
                if (jarr1.size() != jarr2.size()) {
                    return false;
                } else {
                    int i = 0;
                    // Iterate JSON Array to JSON Elements
                    for (JsonElement je : jarr1) {
                        isEqual = isEqual && compareJson(je, jarr2.get(i));
                        i++;
                    }
                }
            }

            // Check whether both jsonElement are null
            else if (json1.isJsonNull() && json2.isJsonNull()) {
                return true;
            }

            // Check whether both jsonElement are primitives
            else if (json1.isJsonPrimitive() && json2.isJsonPrimitive()) {
                if (json1.equals(json2)) {
                    return true;
                } else {
                    return false;
                }
            } else {
                return false;
            }
        } else if (json1 == null && json2 == null) {
            return true;
        } else {
            return false;
        }
        return isEqual;
    }

答案 21 :(得分:0)

JSON.areEqual(json1, json2); //using BlobCity Java Commons

https://tech.blobcity.com/2018/09/02/json-equals-in-java-to-compare-two-jsons

答案 22 :(得分:0)

看着答案,我尝试了JSONAssert,但是失败了。所以我将Jackson与zjsonpatch一起使用。我在SO答案here中张贴了详细信息。

答案 23 :(得分:0)

toMap()中的

JSONObject已经可以很好地与嵌套对象和数组一起使用了。

由于java.util.Map接口指定要检查映射而不是顺序,因此比较Maps很好,而且也是递归的。

json1 = new JSONObject("{...}");
json2 = new JSONObject("{...}");
json1.toMap().equals(json2.toMap());

它可以与任何顺序和嵌套元素一起正常工作。

它将,但是可以使用多余/被忽略的元素。如果知道这些,则可以在调用地图上的equals之前将其删除。

答案 24 :(得分:0)

对于像我这样想与杰克逊这样做的人,你可以使用json-unit

JsonAssert.assertJsonEquals(jsonNode1, jsonNode2);

这些错误可以提供有关不匹配类型的有用反馈:

java.lang.AssertionError: JSON documents have different values:
Different value found in node "heading.content[0].tag[0]". Expected 10209, got 10206.

答案 25 :(得分:0)

我所做的是使用gson将json转换为地图,并使用assertj比较这些地图:

Map<Object, Object> resMap = gson.fromJson(res, new TypeToken<Map<Object, Object>>() {}.getType());
Map<Object, Object> expectedMap = gson.fromJson(expected, new TypeToken<Map<Object, Object>>() {}.getType());
Assertions.assertThat(resMap).usingRecursiveComparison().isEqualTo(expectedMap);

结果是递归地比较所有属性!!!

答案 26 :(得分:0)

这是使用 Jackson ObjectMapper 的代码。要了解更多信息,请阅读this article

import com.fasterxml.jackson.*

boolean compareJsonPojo(Object pojo1, Object pojo2) {
        try {
            ObjectMapper mapper = new ObjectMapper();
            String str1 = mapper.writeValueAsString(pojo1);
            String str2 = mapper.writeValueAsString(pojo2);
            return mapper.readTree(str1).equals(mapper.readTree(str2));
        } catch (JsonProcessingException e) {
            throw new AssertionError("Error comparing JSON objects: " + e.getMessage());
        }
    }

答案 27 :(得分:0)

ModelAssert - https://github.com/webcompere/model-assert 执行此操作。默认情况下,它更喜欢 JSON 有序,但它可以使用对象键和数组元素的宽松顺序:

assertJson(json1)
   .where().keysInAnyOrder().arrayInAnyOrder()
   .isEqualTo(json2);

这个断言是 AssertJ 风格 - 即使用流畅的 DSL。 ModelAssert 还可用于构建具有相同 DSL 的 Hamcrest 或 Mockito 匹配器。

Json 可以是 StringFile、Jackson JsonNode,甚至是自发转换为 JSON 进行比较的 POJO。

还支持 yml。

答案 28 :(得分:-4)

这个解决方案对我来说,工作非常好:

try {           
                // Getting The Array "Courses" from json1 & json2   
                Courses1 =json1.getJSONArray(TAG_COURSES1);
                Courses2 = json2.getJSONArray(TAG_COURSES);

                //LOOP FOR JSON1
                for(int i = 0; i < Courses1.length(); i++){
                    //LOOP FOR JSON2
                    for(int ii = 0; ii < Courses2.length(); ii++){
                        JSONObject courses1 = Courses1.getJSONObject(i);
                        JSONObject courses2 = Courses2.getJSONObject(ii);

                        // Storing each json1 item in variable
                        int courseID1 = courses1.getInt(TAG_COURSEID1);
                        Log.e("COURSEID2:", Integer.toString(courseID1));
                        String Rating1 = courses1.getString(TAG_RATING1);
                        int Status1 = courses1.getInt(TAG_STATUS1);
                        Log.e("Status1:", Integer.toString(Status1));      //Put the actual value for Status1 in log.             

                        // Storing each json2 item in variable
                        int courseID2 = courses2.getInt(TAG_COURSEID);
                        Log.e("COURSEID2:", Integer.toString(courseID));   //Put the actual value for CourseID in log
                        String Title2 = courses2.getString(TAG_TITLE);                      
                        String instructor2 = courses2.getString(TAG_INSTRUCTOR);
                        String length2 = courses2.getString(TAG_LENGTH);
                        String rating2 = courses2.getString(TAG_RATING);
                        String subject2 = courses2.getString(TAG_SUBJECT);
                        String description2 = courses2.getString(TAG_DESCRIPTION);

                        //Status1 = 5 from json1; Incomplete, Status1 =-1 Complete 
                        if(Status1 == 5 && courseID2 == courseID1){                                  

                        // creating new HashMap
                        HashMap<String, String> map = new HashMap<String, String>();         
                        //Storing the elements if condition is true.
                        map.put(TAG_COURSEID, Integer.toString(courseID2)); //pend for compare
                        map.put(TAG_TITLE, Title2);
                        map.put(TAG_INSTRUCTOR, instructor2);
                        map.put(TAG_LENGTH, length2);
                        map.put(TAG_RATING, rating2);
                        map.put(TAG_SUBJECT, subject2); //show it
                        map.put(TAG_DESCRIPTION, description2);

                        //adding HashList to ArrayList
                        contactList.add(map);
                        }//if
                    }//for2 (json2)
                } //for1 (json1)                
            }//Try

希望这有助于他人。