1. 引言

比较JSON对象集合的相等性时,元素顺序的不确定性会带来挑战。虽然Jackson和AssertJ等库可以处理,但像JSONassert和hamcrest-json这样的专用工具能更可靠地解决这类问题。

本文将探讨如何使用JSONassert和hamcrest-json比较JSON对象集合,重点忽略元素顺序。

2. 问题场景

处理JSON对象集合时,数据源不同可能导致元素顺序变化。考虑以下两个JSON数组:

[
  {"id": 1, "name": "Alice", "address": {"city": "NY", "street": "5th Ave"}},
  {"id": 2, "name": "Bob", "address": {"city": "LA", "street": "Sunset Blvd"}}
]
[
  {"id": 2, "name": "Bob", "address": {"city": "LA", "street": "Sunset Blvd"}},
  {"id": 1, "name": "Alice", "address": {"city": "NY", "street": "5th Ave"}}
]

尽管数据完全相同,但顺序不同。直接字符串比较会因顺序差异失败。我们将这些数组定义为Java变量:

String jsonArray1 = "["
        + "{\"id\": 1, \"name\": \"Alice\", \"address\": {\"city\": \"NY\", \"street\": \"5th Ave\"}}, "
        + "{\"id\": 2, \"name\": \"Bob\", \"address\": {\"city\": \"LA\", \"street\": \"Sunset Blvd\"}}"
        + "]";

String jsonArray2 = "["
        + "{\"id\": 2, \"name\": \"Bob\", \"address\": {\"city\": \"LA\", \"street\": \"Sunset Blvd\"}}, "
        + "{\"id\": 1, \"name\": \"Alice\", \"address\": {\"city\": \"NY\", \"street\": \"5th Ave\"}}"
        + "]";

3. 使用JSONassert比较JSON

JSONassert提供灵活的JSON比较方式,支持按JSON结构而非字符串比较。其LENIENT模式可忽略元素顺序:

@Test
public void givenJsonArrays_whenUsingJSONAssertIgnoringOrder_thenEqual() throws JSONException {
    JSONAssert.assertEquals(jsonArray1, jsonArray2, JSONCompareMode.LENIENT);
}

JSONCompareMode.LENIENT使JSONassert专注于内容而非顺序。当数据相同但顺序可变时,这是理想选择

3.1 忽略额外字段

LENIENT模式还能忽略JSON对象中的额外字段。这对忽略元数据或时间戳等无关字段特别有用

@Test
public void givenJsonWithExtraFields_whenIgnoringExtraFields_thenEqual() throws JSONException {
    String jsonWithExtraFields = "["
            + "{\"id\": 1, \"name\": \"Alice\", \"address\": {\"city\": \"NY\", \"street\": \"5th Ave\"}, \"age\": 30}, "
            + "{\"id\": 2, \"name\": \"Bob\", \"address\": {\"city\": \"LA\", \"street\": \"Sunset Blvd\"}, \"age\": 25}"
            + "]";

    JSONAssert.assertEquals(jsonArray1, jsonWithExtraFields, JSONCompareMode.LENIENT);
}

✅ 即使包含age等额外字段,比较仍会成功。

4. 使用hamcrest-json进行JSON匹配

hamcrest-json是Hamcrest的JSON专用插件,提供更丰富的断言功能。其核心方法allowingAnyArrayOrdering()可忽略数组顺序

@Test
public void givenJsonCollection_whenIgnoringOrder_thenEqual() {
    assertThat(jsonArray1, sameJSONAs(jsonArray2).allowingAnyArrayOrdering());
}

sameJSONAs()匹配器结合顺序忽略,实现精确比较。

4.1 忽略额外字段

hamcrest-json的allowingExtraUnexpectedFields()方法可处理额外字段:

@Test
public void givenJsonWithUnexpectedFields_whenIgnoringUnexpectedFields_thenEqual() {
    String jsonWithUnexpectedFields = "["
            + "{\"id\": 1, \"name\": \"Alice\", \"address\": {\"city\": \"NY\", \"street\": \"5th Ave\"}, \"extraField\": \"ignoreMe\"}, "
            + "{\"id\": 2, \"name\": \"Bob\", \"address\": {\"city\": \"LA\", \"street\": \"Sunset Blvd\"}}"
            + "]";

    assertThat(jsonWithUnexpectedFields, sameJSONAs(jsonArray1).allowingExtraUnexpectedFields());
}

⚠️ 组合使用allowingExtraUnexpectedFields()allowingAnyArrayOrdering()可实现健壮的JSON比较。

5. 总结

本文展示了如何使用JSONassert和hamcrest-json比较JSON对象集合时忽略元素顺序。这些专用库比手动解析JSON更可靠,且使用更便捷。

完整代码示例可在GitHub获取。


原始标题:Assert Collection of JSON Objects Ignoring Order | Baeldung