比较两个 JSON 时忽略特定 nodes/attributes
Ignore specific nodes/attributes while comparing two JSONs
我想比较两个 JSON 字符串,这是一个巨大的层次结构,并且想知道它们的值有何不同。但是有些值是在运行时生成的并且是动态的。我想在比较中忽略那些特定节点。
我目前正在使用 JSON来自 org.SkyScreamer 的断言进行比较。它给了我很好的控制台输出但不忽略任何属性。
例如
java.lang.AssertionError messageHeader.sentTime
expected:null
got:09082016 18:49:41.123
现在这是动态的,应该忽略。像
JSONAssert.assertEquals(expectedJSONString, actualJSONString,JSONCompareMode, *list of attributes to be ignored*)
如果有人在 JSONAssert 中提出解决方案,那就太好了。但是也欢迎其他方式。
您可以为此使用自定义。例如,如果您需要忽略名为 "timestamp" 的顶级属性,请使用:
JSONAssert.assertEquals(expectedResponseBody, responseBody,
new CustomComparator(JSONCompareMode.LENIENT,
new Customization("timestamp", (o1, o2) -> true)));
也可以使用像 "entry.id" 这样的路径表达式。在您的自定义中,您可以使用任何您喜欢的方法来比较这两个值。上面的例子总是 returns true,不管期望值和实际值是多少。如果需要,您可以在那里做更复杂的事情。
完全可以忽略多个属性的值,例如:
@Test
public void ignoringMultipleAttributesWorks() throws JSONException {
String expected = "{\"timestamp\":1234567, \"a\":5, \"b\":3 }";
String actual = "{\"timestamp\":987654, \"a\":1, \"b\":3 }";
JSONAssert.assertEquals(expected, actual,
new CustomComparator(JSONCompareMode.LENIENT,
new Customization("timestamp", (o1, o2) -> true),
new Customization("a", (o1, o2) -> true)
));
}
使用自定义时有一个警告:要以自定义方式比较其值的属性必须存在于实际 JSON 中。如果您希望比较成功,即使该属性根本不存在,您也必须重写 CustomComparator,例如:
@Test
public void extendingCustomComparatorToAllowToCompletelyIgnoreCertainAttributes() throws JSONException {
// AttributeIgnoringComparator completely ignores some of the expected attributes
class AttributeIgnoringComparator extends CustomComparator{
private final Set<String> attributesToIgnore;
private AttributeIgnoringComparator(JSONCompareMode mode, Set<String> attributesToIgnore, Customization... customizations) {
super(mode, customizations);
this.attributesToIgnore = attributesToIgnore;
}
protected void checkJsonObjectKeysExpectedInActual(String prefix, JSONObject expected, JSONObject actual, JSONCompareResult result) throws JSONException {
Set<String> expectedKeys = getKeys(expected);
expectedKeys.removeAll(attributesToIgnore);
for (String key : expectedKeys) {
Object expectedValue = expected.get(key);
if (actual.has(key)) {
Object actualValue = actual.get(key);
compareValues(qualify(prefix, key), expectedValue, actualValue, result);
} else {
result.missing(prefix, key);
}
}
}
}
String expected = "{\"timestamp\":1234567, \"a\":5}";
String actual = "{\"a\":5}";
JSONAssert.assertEquals(expected, actual,
new AttributeIgnoringComparator(JSONCompareMode.LENIENT,
new HashSet<>(Arrays.asList("timestamp")))
);
}
(通过这种方法,您仍然可以使用自定义项以您想要的方式比较其他属性的值。)
首先是open issue。
在我的测试中,我在 JsonUtil
class 的帮助下将来自控制器的 json 与实际对象进行比较 serialization/deserialization:
public class JsonUtil {
public static <T> List<T> readValues(String json, Class<T> clazz) {
ObjectReader reader = getMapper().readerFor(clazz);
try {
return reader.<T>readValues(json).readAll();
} catch (IOException e) {
throw new IllegalArgumentException("Invalid read array from JSON:\n'" + json + "'", e);
}
}
public static <T> T readValue(String json, Class<T> clazz) {
try {
return getMapper().readValue(json, clazz);
} catch (IOException e) {
throw new IllegalArgumentException("Invalid read from JSON:\n'" + json + "'", e);
}
}
public static <T> String writeValue(T obj) {
try {
return getMapper().writeValueAsString(obj);
} catch (JsonProcessingException e) {
throw new IllegalStateException("Invalid write to JSON:\n'" + obj + "'", e);
}
}
为了忽略特定的对象字段,我添加了新方法:
public static <T> String writeIgnoreProps(T obj, String... ignoreProps) {
try {
Map<String, Object> map = getMapper().convertValue(obj, new TypeReference<Map<String, Object>>() {});
for (String prop : ignoreProps) {
map.remove(prop);
}
return getMapper().writeValueAsString(map);
} catch (JsonProcessingException e) {
throw new IllegalStateException("Invalid write to JSON:\n'" + obj + "'", e);
}
}
我的测试断言现在看起来像这样:
mockMvc.perform(get(REST_URL))
.andExpect(status().isOk())
.andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
.andExpect(content().json(JsonUtil.writeIgnoreProps(USER, "registered")))
您可以使用 JsonUnit 它具有您正在寻找的功能,我们可以忽略字段、路径和空值等。检查一下更多信息。至于这个例子,你可以忽略这样的路径
assertJsonEquals(
"{\"root\":{\"test\":1, \"ignored\": 2}}",
"{\"root\":{\"test\":1, \"ignored\": 1}}",
whenIgnoringPaths("root.ignored")
);
有时您需要在比较时忽略某些值。可以像这样使用 ${json-unit.ignore} 占位符
assertJsonEquals("{\"test\":\"${json-unit.ignore}\"}",
"{\n\"test\": {\"object\" : {\"another\" : 1}}}");
感谢@dknaus 的详细解答。尽管此解决方案在 STRICT 模式下不起作用,并且 checkJsonObjectKeysExpectedInActual
方法代码需要替换为以下代码 [如 @tk-gospodinov 所建议]:
for (String attribute : attributesToIgnore) {
expected.remove(attribute);
super.checkJsonObjectKeysExpectedInActual(prefix, expected, actual, result);
}
我想比较两个 JSON 字符串,这是一个巨大的层次结构,并且想知道它们的值有何不同。但是有些值是在运行时生成的并且是动态的。我想在比较中忽略那些特定节点。
我目前正在使用 JSON来自 org.SkyScreamer 的断言进行比较。它给了我很好的控制台输出但不忽略任何属性。
例如
java.lang.AssertionError messageHeader.sentTime
expected:null
got:09082016 18:49:41.123
现在这是动态的,应该忽略。像
JSONAssert.assertEquals(expectedJSONString, actualJSONString,JSONCompareMode, *list of attributes to be ignored*)
如果有人在 JSONAssert 中提出解决方案,那就太好了。但是也欢迎其他方式。
您可以为此使用自定义。例如,如果您需要忽略名为 "timestamp" 的顶级属性,请使用:
JSONAssert.assertEquals(expectedResponseBody, responseBody,
new CustomComparator(JSONCompareMode.LENIENT,
new Customization("timestamp", (o1, o2) -> true)));
也可以使用像 "entry.id" 这样的路径表达式。在您的自定义中,您可以使用任何您喜欢的方法来比较这两个值。上面的例子总是 returns true,不管期望值和实际值是多少。如果需要,您可以在那里做更复杂的事情。
完全可以忽略多个属性的值,例如:
@Test
public void ignoringMultipleAttributesWorks() throws JSONException {
String expected = "{\"timestamp\":1234567, \"a\":5, \"b\":3 }";
String actual = "{\"timestamp\":987654, \"a\":1, \"b\":3 }";
JSONAssert.assertEquals(expected, actual,
new CustomComparator(JSONCompareMode.LENIENT,
new Customization("timestamp", (o1, o2) -> true),
new Customization("a", (o1, o2) -> true)
));
}
使用自定义时有一个警告:要以自定义方式比较其值的属性必须存在于实际 JSON 中。如果您希望比较成功,即使该属性根本不存在,您也必须重写 CustomComparator,例如:
@Test
public void extendingCustomComparatorToAllowToCompletelyIgnoreCertainAttributes() throws JSONException {
// AttributeIgnoringComparator completely ignores some of the expected attributes
class AttributeIgnoringComparator extends CustomComparator{
private final Set<String> attributesToIgnore;
private AttributeIgnoringComparator(JSONCompareMode mode, Set<String> attributesToIgnore, Customization... customizations) {
super(mode, customizations);
this.attributesToIgnore = attributesToIgnore;
}
protected void checkJsonObjectKeysExpectedInActual(String prefix, JSONObject expected, JSONObject actual, JSONCompareResult result) throws JSONException {
Set<String> expectedKeys = getKeys(expected);
expectedKeys.removeAll(attributesToIgnore);
for (String key : expectedKeys) {
Object expectedValue = expected.get(key);
if (actual.has(key)) {
Object actualValue = actual.get(key);
compareValues(qualify(prefix, key), expectedValue, actualValue, result);
} else {
result.missing(prefix, key);
}
}
}
}
String expected = "{\"timestamp\":1234567, \"a\":5}";
String actual = "{\"a\":5}";
JSONAssert.assertEquals(expected, actual,
new AttributeIgnoringComparator(JSONCompareMode.LENIENT,
new HashSet<>(Arrays.asList("timestamp")))
);
}
(通过这种方法,您仍然可以使用自定义项以您想要的方式比较其他属性的值。)
首先是open issue。
在我的测试中,我在 JsonUtil
class 的帮助下将来自控制器的 json 与实际对象进行比较 serialization/deserialization:
public class JsonUtil {
public static <T> List<T> readValues(String json, Class<T> clazz) {
ObjectReader reader = getMapper().readerFor(clazz);
try {
return reader.<T>readValues(json).readAll();
} catch (IOException e) {
throw new IllegalArgumentException("Invalid read array from JSON:\n'" + json + "'", e);
}
}
public static <T> T readValue(String json, Class<T> clazz) {
try {
return getMapper().readValue(json, clazz);
} catch (IOException e) {
throw new IllegalArgumentException("Invalid read from JSON:\n'" + json + "'", e);
}
}
public static <T> String writeValue(T obj) {
try {
return getMapper().writeValueAsString(obj);
} catch (JsonProcessingException e) {
throw new IllegalStateException("Invalid write to JSON:\n'" + obj + "'", e);
}
}
为了忽略特定的对象字段,我添加了新方法:
public static <T> String writeIgnoreProps(T obj, String... ignoreProps) {
try {
Map<String, Object> map = getMapper().convertValue(obj, new TypeReference<Map<String, Object>>() {});
for (String prop : ignoreProps) {
map.remove(prop);
}
return getMapper().writeValueAsString(map);
} catch (JsonProcessingException e) {
throw new IllegalStateException("Invalid write to JSON:\n'" + obj + "'", e);
}
}
我的测试断言现在看起来像这样:
mockMvc.perform(get(REST_URL))
.andExpect(status().isOk())
.andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
.andExpect(content().json(JsonUtil.writeIgnoreProps(USER, "registered")))
您可以使用 JsonUnit 它具有您正在寻找的功能,我们可以忽略字段、路径和空值等。检查一下更多信息。至于这个例子,你可以忽略这样的路径
assertJsonEquals(
"{\"root\":{\"test\":1, \"ignored\": 2}}",
"{\"root\":{\"test\":1, \"ignored\": 1}}",
whenIgnoringPaths("root.ignored")
);
有时您需要在比较时忽略某些值。可以像这样使用 ${json-unit.ignore} 占位符
assertJsonEquals("{\"test\":\"${json-unit.ignore}\"}",
"{\n\"test\": {\"object\" : {\"another\" : 1}}}");
感谢@dknaus 的详细解答。尽管此解决方案在 STRICT 模式下不起作用,并且 checkJsonObjectKeysExpectedInActual
方法代码需要替换为以下代码 [如 @tk-gospodinov 所建议]:
for (String attribute : attributesToIgnore) {
expected.remove(attribute);
super.checkJsonObjectKeysExpectedInActual(prefix, expected, actual, result);
}