1. 介紹
確定 JSON 對象集合的相等性可能具有挑戰性,尤其當集合中元素的順序無法保證時。雖然像 Jackson 和 AssertJ 這樣的庫可以被使用,但像 JSONassert 和 hamcrest-json 這樣更專業的工具旨在更可靠地處理這種情況。
在本教程中,我們將探索如何比較 JSON 對象集合,重點是忽略元素順序,使用 JSONassert 和 hamcrest-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"}}
]雖然這些數組包含相同元素,但順序不同。直接字符串比較這些數組會因為順序差異而失敗,儘管它們的數據完全相同
讓我們將這些JSON數組定義為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 模式,JSONAssert 僅關注內容,忽略順序:
@Test
public void givenJsonArrays_whenUsingJSONAssertIgnoringOrder_thenEqual() throws JSONException {
JSONAssert.assertEquals(jsonArray1, jsonArray2, JSONCompareMode.LENIENT);
}在本測試中,JSONCompareMode.LENIENT 模式允許我們斷言相等性,同時忽略元素順序。 這使得 JSONassert 適用於我們期望數據相同,但元素順序可能不同的情況
3.1. 忽略多餘字段,使用 JSONassert
JSONassert 同樣允許忽略 JSON 對象中的多餘字段,通過使用 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);
}在本示例中,測試驗證 jsonArray1 與 jsonWithExtraFields 的等效性,允許在比較中包含額外的字段,例如 age。
4. 使用 hamcrest-json 進行 JSON 匹配
除了 JSONassert,我們還可以利用 hamcrest-json,這是一個專門為 Hamcrest 設計的 JSON 斷言插件。該插件基於 Hamcrest 的匹配器功能,允許我們在 JUnit 中編寫表達豐富且易讀的 JSON 斷言。
hamcrest-json 最有用的功能之一是 allowingAnyArrayOrdering() 方法。它允許我們比較 JSON 數組時忽略它們的順序:
@Test
public void givenJsonCollection_whenIgnoringOrder_thenEqual() {
assertThat(jsonArray1, sameJSONAs(jsonArray2).allowingAnyArrayOrdering());
}這種方法確保 JSON 比較忽略數組中元素的順序,使用 sameJSONAs() 匹配器。
4.1. 忽略額外字段,使用 hamcrest-json
除了忽略數組順序之外,hamcrest-json 還提供了 allowingExtraUnexpectedFields() 實用方法,用於處理額外的字段。 此方法允許我們忽略在一種 JSON 對象中存在,但在另一種 JSON 對象中不存在的字段:
@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());
}在本示例中,我們驗證 jsonWithUnexpectedFields 等於 jsonArray1,即使它包含一個額外的字段。通過結合使用 allowingExtraUnexpectedFields() 和 allowingAnyArrayOrdering(),我們確保進行一種健壯的比較,該比較專注於匹配我們 JSON 數組之間的數據。
5. 結論
在本文中,我們展示瞭如何通過使用專門的庫(如JSONassert和hamcrest-json)比較 JSON 對象集合,同時忽略元素順序的方法。與手動解析 JSON 到 Java 對象相比,這些庫提供了一種更直接、更可靠、更易於使用的方案。