我正在寻找一个JSON解析库,支持比较忽略子顺序的两个JSON对象,特别是用于从web服务返回的单元测试JSON。
有任何主要的JSON库支持这一点吗?org。Json库只是做一个引用比较。
我将采用http://json.org/java/库,并修改JSONObject和JSONArray的equals方法来进行深度相等测试。为了确保它可以工作,不考虑子元素的顺序,你所需要做的就是用TreeMap替换内部映射,或者使用类似Collections.sort()的东西。
equals
TreeMap
Collections.sort()
作为一个通用的架构点,我通常建议不要让对特定序列化格式的依赖超出您的存储/网络层;因此,我首先建议您考虑测试您自己的应用程序对象之间的相等性,而不是它们的JSON表示。
话虽如此,我目前是杰克逊的忠实粉丝,我对他们的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; }
你可以尝试使用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]>
我做的一件事是将两个对象读入HashMap,然后与常规的assertEquals()进行比较。它将调用哈希映射的equals()方法,该方法将递归地比较其中的所有对象(它们将是其他哈希映射或一些单值对象,如字符串或整数)。这是使用Codehaus的Jackson JSON解析器完成的。
assertEquals(mapper.readValue(expectedJson, new TypeReference<HashMap<String, Object>>(){}), mapper.readValue(actualJson, new TypeReference<HashMap<String, Object>>(){}));
如果JSON对象是一个数组,则可以使用类似的方法。
使用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);
编辑:由于GSON v2.8.6,实例方法JsonParser.parse已弃用。你必须使用静态方法JsonParser.parseString:
JsonParser.parse
JsonParser.parseString
JsonElement o1 = JsonParser.parseString("{a : {a : 2}, b : 2}"); JsonElement o2 = JsonParser.parseString("{b : 2, a : {a : 2}}"); assertEquals(o1, o2);
你可以试试JsonUnit。它可以比较两个JSON对象并报告差异。它建在杰克逊的顶部。
例如
assertThatJson("{\"test\":1}").isEqualTo("{\n\"test\": 2\n}");
结果
java.lang.AssertionError: JSON documents are different: Different value found in node "test". Expected 1, got 2.
试试Skyscreamer的JSONAssert吧。
它的的非严格模式有两个主要优点,使其不那么脆弱:
在严格模式下,它更像json-lib的test类。
测试是这样的:
@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对象时非常重要。
org。我已经推出了自己的解决方案,一个与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; }
如果您已经在使用JUnit,那么最新版本现在使用Hamcrest。它是一个通用的匹配框架(对单元测试特别有用),可以扩展以构建新的匹配器。
有一个名为hamcrest-json的小型开源库,具有json感知匹配。它有良好的文档、测试和支持。以下是一些有用的链接:
hamcrest-json
使用JSON库org.json.simple对象的示例代码:
org.json.simple
Assert.assertThat( jsonObject1.toJSONString(), SameJSONAs.sameJSONAs(jsonObject2.toJSONString()));
可选地,您可以(1)允许“任意顺序”数组和(2)忽略额外的字段。
由于Java有多种JSON库(Jackson, GSON, json-lib等),因此hamcrest-json支持JSON文本(如java.lang.String),以及本地支持来自Douglas Crockford的JSON库org.json的对象是有用的。
Jackson
GSON
json-lib
java.lang.String
org.json
最后,如果不使用JUnit,可以直接使用Hamcrest进行断言。(我在这里写过。)
试试这个:
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; }
我会这么做,
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);
对于像我这样想用Jackson做这个的人,你可以使用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.
使用这个库:https://github.com/lukas-krecan/JsonUnit
Pom:
<dependency> <groupId>net.javacrumbs.json-unit</groupId> <artifactId>json-unit-assertj</artifactId> <version>2.24.0</version> <scope>test</scope> </dependency>
IGNORING_ARRAY_ORDER -忽略数组中的顺序
assertThatJson("{\"test\":[1,2,3]}") .when(Option.IGNORING_ARRAY_ORDER) .isEqualTo("{\"test\": [3,2,1]}");
你可以使用zjsonpatch库,它根据RFC 6902 (JSON Patch)显示差异信息。它很容易使用。请访问其说明页面了解其用法
我正在使用这个,并为我工作良好(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; } } }
我知道它通常只用于测试,但你可以在Hamcrest JSON中使用Hamcrest JSON comparitorSameJSONAs。
Hamcrest JSON SameJSONAs
空手道正是你要找的。这里有一个例子:
* def myJson = { foo: 'world', hey: 'ho', zee: [5], cat: { name: 'Billie' } } * match myJson = { cat: { name: 'Billie' }, hey: 'ho', foo: 'world', zee: [5] }
(免责声明:dev here)
其他方法似乎都不太合适,所以我写下了这个:
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; }
下面的代码将更有助于比较两个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; }
// 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
JSON.areEqual(json1, json2); //using BlobCity Java Commons
https://tech.blobcity.com/2018/09/02/json-equals-in-java-to-compare-two-jsons
查看答案,我尝试了JSONAssert,但失败了。所以我用Jackson和zjsonpatch。我在SO答案在这里中发布了详细信息。
JSONObject中的toMap()已经可以很好地处理嵌套对象和数组。
JSONObject
toMap()
因为java.util.Map接口指定检查映射而不是顺序,所以比较map是可以的,也是递归的。
json1 = new JSONObject("{...}"); json2 = new JSONObject("{...}"); json1.toMap().equals(json2.toMap());
它可以很好地处理任何顺序和嵌套元素。
然而,它将不用于额外的/被忽略的元素。如果这些是已知的,你可以在调用映射上的equals之前删除它们。
但是org.json 做支持这个!使用similar()代替equals()。
similar()
equals()
我所做的是使用gson将jsons转换为map,并使用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);
结果是递归地比较了所有属性!!
下面是使用Jackson ObjectMapper的代码。要了解更多,请阅读这篇文章。
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()); } }
这可能会帮助那些使用Spring框架的人。你可以在内部重用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); }
ModelAssert - https://github.com/webcompere/model-assert做到了这一点。默认情况下,它更喜欢JSON是有序的,但它可以使用对象键和数组元素的宽松顺序:
assertJson(json1) .where().keysInAnyOrder().arrayInAnyOrder() .isEqualTo(json2);
这个断言是AssertJ风格——即使用一个流畅的DSL。ModelAssert还可以用于使用相同的DSL构建Hamcrest或Mockito匹配器。
Json可以是String, File, Jackson JsonNode,甚至是一个自发转换为Json进行比较的POJO。
String
File
JsonNode
还支持yml。