关闭。此问题不符合 Stack Overflow 准则。它目前不接受答案。我们不允许提出有关书籍、工具、软件库等建议的问题。您可以编辑问题,以便可以用事实和引用来回答它。 9 个月前关闭。社区在 9 个月前审查了是否重新打开此问题并将其关闭:原始关闭原因未解决 改进此问题
我正在寻找一个支持比较两个忽略子顺序的 JSON 对象的 JSON 解析库,专门用于对从 Web 服务返回的 JSON 进行单元测试。
是否有任何主要的 JSON 库支持这一点? org.json 库只是做一个参考比较。
toString()
将对象转换为 JSON
字符串。
试试 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 对象时很重要。
作为一般架构点,我通常建议不要让对特定序列化格式的依赖超出您的存储/网络层;因此,我首先建议您考虑测试您自己的应用程序对象之间的相等性,而不是它们的 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;
}
使用 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
:
JsonElement o1 = JsonParser.parseString("{a : {a : 2}, b : 2}");
JsonElement o2 = JsonParser.parseString("{b : 2, a : {a : 2}}");
assertEquals(o1, o2);
我会做以下事情,
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);
使用此库:https://github.com/lukas-krecan/JsonUnit
绒球:
<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]}");
您可以尝试使用 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]>
JSONAssert.assertJsonEquals( "{foo: 'bar', list: [{test: '1'}, {rest: '2'}] }", "{ foo: 'bar', list: [{rest: '2'}, {test: '1'}] }");
带有消息:junit.framework.AssertionFailedError: : : objects differed at key [list];: arrays first differed at element [0];: objects differed at key [test];
如果您已经在使用 JUnit,那么最新版本现在使用 Hamcrest。它是一个通用匹配框架(对单元测试特别有用),可以扩展以构建新的匹配器。
有一个名为 hamcrest-json
的小型开源库,其中包含可识别 JSON 的匹配项。它有很好的文档记录、测试和支持。以下是一些有用的链接:
源代码
主页
主匹配器的 Javadocs:
使用来自 JSON 库 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
。
最后,如果您不使用 JUnit,您可以直接使用 Hamcrest 进行断言。 (I wrote about it here.)
你可以试试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.
我正在使用它,对我来说效果很好(使用 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;
}
}
}
我做了一件事,它产生了奇迹,就是将这两个对象读入 HashMap,然后与常规的 assertEquals() 进行比较。它将调用 hashmap 的 equals() 方法,该方法将递归地比较内部的所有对象(它们将是其他 hashmap 或一些单值对象,如字符串或整数)。这是使用 Codehaus 的 Jackson JSON 解析器完成的。
assertEquals(mapper.readValue(expectedJson, new TypeReference<HashMap<String, Object>>(){}), mapper.readValue(actualJson, new TypeReference<HashMap<String, Object>>(){}));
如果 JSON 对象是一个数组,则可以使用类似的方法。
对于 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;
}
val1
为 null,您将从此代码 if (!val1.equals(val2)) {
中获得 NullPointerException
js1
不是 null
时,您没有考虑 js2
是否是 null
我将使用 http://json.org/java/ 的库,并修改 JSONObject 和 JSONArray 的 equals
方法以进行深度相等测试。为了确保它的工作不受子级顺序的影响,您需要做的就是用 TreeMap
替换内部映射,或者使用类似 Collections.sort()
的东西。
尝试这个:
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;
}
if (obj1Array.length() != obj2Array.length())
不能确保 all 元素一致吗?
Karate 正是您正在寻找的。这是一个例子:
* def myJson = { foo: 'world', hey: 'ho', zee: [5], cat: { name: 'Billie' } }
* match myJson = { cat: { name: 'Billie' }, hey: 'ho', foo: 'world', zee: [5] }
(免责声明:此处为开发人员)
我所做的是使用 gson 将 jsons 转换为地图并使用 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);
结果是递归地对所有属性进行了很好的比较!!!
这可能会帮助那些使用 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);
}
为了比较 json,我建议使用我的库 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
对于像我这样想要与 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.
似乎没有其他方法可以正常工作,所以我写了这个:
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;
}
JSON.areEqual(json1, json2); //using BlobCity Java Commons
https://tech.blobcity.com/2018/09/02/json-equals-in-java-to-compare-two-jsons
JSONObject
中的 toMap()
已经可以很好地处理嵌套对象和数组。
由于 java.util.Map 接口指定检查映射而不是顺序,因此比较 Maps 很好并且也是递归的。
json1 = new JSONObject("{...}");
json2 = new JSONObject("{...}");
json1.toMap().equals(json2.toMap());
它适用于任何顺序和嵌套元素。
它不会不但是可以处理额外/忽略的元素。如果这些是已知的,您可以在调用地图上的 equals
之前删除它们。
这是使用 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());
}
}
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。
还有对 yml 的支持。
不定期副业成功案例分享
JSONAssert.assertEquals(expected, data, JSONCompareMode.NON_EXTENSIBLE);
NON_EXTENSIBLE 模式意味着任何新的或缺失的字段都会导致失败,但订单不会。使用 false 将启动宽松模式,该模式不会报告任何额外或缺失的子元素。