1. 概述
在本教程中,我們將探討如何以 Map 格式讀取 JSON 文檔,並進行比較。 此外,我們還將研究如何查找兩個 Map 之間的差異。
2. 轉換為Map
首先,我們將探討如何將 JSON 文檔轉換為 Map。讓我們先來看一下我們將用於測試的 JSON 對象。
讓我們創建一個名為 first.json 的文件,內容如下:
{
"name": "John",
"age": 30,
"cars": [
"Ford",
"BMW"
],
"address": {
"street": "Second Street",
"city": "New York"
},
"children": [
{
"name": "Sara",
"age": 5
},
{
"name": "Alex",
"age": 3
}
]
}
同樣,我們創建一個名為 second.json 的文件,內容如下:
{
"name": "John",
"age": 30,
"cars": [
"Ford",
"Audi"
],
"address": {
"street": "Main Street",
"city": "New York"
},
"children": [
{
"name": "Peter",
"age": 5
},
{
"name": "Cathy",
"age": 10
}
]
}
如我們所見,以上JSON文檔之間存在兩個差異:
- cars數組的值不同
- street鍵在address對象中的值不同
- children數組存在多個差異
2.1. 使用 Jackson
Jackson 是一個流行的用於 JSON 操作的庫。我們可以使用 Jackson 將 JSON 轉換為一個 Map</em title="Map>。
讓我們先添加 Jackson 依賴項:https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>現在我們可以使用 Jackson 將 JSON 文檔轉換為 Map:
class JsonUtils {
public static Map<String, Object> jsonFileToMap(String path) throws IOException {
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(new File(path), new TypeReference<Map<String, Object>>() {});
}
}
在這裏,我們使用ObjectMapper類中的readValue()方法將JSON文檔轉換為Map。該方法接受JSON文檔作為File對象以及TypeReference對象作為參數。
2.2. 使用 Gson
同樣,我們也可以使用 Gson 將 JSON 文檔轉換為一個 Map。 為了實現這一點,我們需要包含以下依賴項:依賴項。
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.11.0</version>
</dependency>現在讓我們來看一下用於轉換 JSON 的代碼:
public static Map<String, Object> jsonFileToMapGson(String path) throws IOException {
Gson gson = new Gson();
return gson.fromJson(new FileReader(path), new TypeToken<Map<String, Object>>() {}.getType());
}
在這裏,我們使用 Gson 類中的 fromJson() 方法將 JSON 文檔轉換為 Map。它接受 JSON 文檔作為 FileReader 對象以及 TypeToken 對象作為參數。
3. 比較 Map 結構
現在我們已經將 JSON 文檔轉換為 Map 結構,接下來讓我們看看不同的比較方法。
3.1. 使用 Guava 的 <em >Map.difference()</em >
Guava 提供了一個 <em >Maps.difference()</em > 方法,可用於比較兩個 <em >Map</em > 對象。 要使用此方法,請將 Guava 依賴項添加到您的項目:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.2.1-jre</version>
</dependency>
現在,讓我們來看一下代碼以比較Map:
@Test
void givenTwoJsonFiles_whenCompared_thenTheyAreDifferent() throws IOException {
Map<String, Object> firstMap = JsonUtils.jsonFileToMap("src/test/resources/first.json");
Map<String, Object> secondMap = JsonUtils.jsonFileToMap("src/test/resources/second.json");
MapDifference<String, Object> difference = Maps.difference(firstFlatMap, secondFlatMap);
difference.entriesDiffering().forEach((key, value) -> {
System.out.println(key + ": " + value.leftValue() + " - " + value.rightValue());
});
assertThat(difference.areEqual()).isFalse();
}
Guava 只能比較一個級別的 Map。 這不適用於我們的情況,因為我們有一個嵌套的 Map。
讓我們看看如何比較我們上面的嵌套 Map。 我們使用 entriesDiffering() 方法來獲取兩個 Map 之間的差異。 這將返回一個包含差異的 Map,其中鍵是值的路徑,值是 MapDifference.ValueDifference 對象。 這個對象包含來自兩個 Map 的值。 如果運行測試,我們將看到兩個 Map 之間的不同鍵及其值:
cars: [Ford, BMW] - [Ford, Audi]
address: {street=Second Street, city=New York} - {street=Main Street, city=New York}
children: [{name=Sara, age=5}, {name=Alex, age=3}] - [{name=Peter, age=5}, {name=Cathy, age=10}]如我們所見,這表明 車輛、地址 和 兒童 字段不同,並且這些差異已列出。然而,這並不能説明哪些嵌套字段導致了這些差異。例如,它並沒有指出 街道 字段在 地址 對象中的差異。
3.2. 平鋪 Maps</h3
為了準確地指出嵌套 Maps 之間的差異,我們將 Maps 平鋪,使每個鍵都指向其值。例如,street 鍵在 address 對象中將被平鋪為 address.street,以此類推。
下面是對此的示例代碼:
class FlattenUtils {
public static Map<String, Object> flatten(Map<String, Object> map) {
return flatten(map, null);
}
private static Map<String, Object> flatten(Map<String, Object> map, String prefix) {
Map<String, Object> flatMap = new HashMap<>();
map.forEach((key, value) -> {
String newKey = prefix != null ? prefix + "." + key : key;
if (value instanceof Map) {
flatMap.putAll(flatten((Map<String, Object>) value, newKey));
} else if (value instanceof List) {
// check for list of primitives
Object element = ((List<?>) value).get(0);
if (element instanceof String || element instanceof Number || element instanceof Boolean) {
flatMap.put(newKey, value);
} else {
// check for list of objects
List<Map<String, Object>> list = (List<Map<String, Object>>) value;
for (int i = 0; i < list.size(); i++) {
flatMap.putAll(flatten(list.get(i), newKey + "[" + i + "]"));
}
}
} else {
flatMap.put(newKey, value);
}
});
return flatMap;
}
}
在這裏,我們使用遞歸來扁平化 Map。對於任何字段,以下條件之一將為真:
- 值可以是 Map(嵌套 JSON 對象)。在這種情況下,我們將使用值作為參數遞歸調用 flatten() 方法。例如,address 對象將被扁平化為 address.street 和 address.city。
- 接下來,我們可以檢查值是否為 List(JSON 數組)。如果數組包含原始值,我們將鍵和值添加到扁平化後的 Map 中。
- 如果數組包含對象,我們將使用每個對象作為參數遞歸調用 flatten() 方法。例如,children 數組將被扁平化為 children[0].name、children[0].age、children[1].name 和 children[1].age。
- 如果值既不是 Map 也不是 List,我們將鍵和值添加到扁平化後的 Map 中。
這將遞歸進行,直到我們到達 Map 的最後一層。此時,我們將會得到一個扁平化後的 Map,其中每個鍵都表示指向值的路徑。
3.3. 測試
現在我們已經對地圖進行了扁平化,接下來讓我們看看如何使用Maps.difference()方法比較它們:
@Test
void givenTwoJsonFiles_whenCompared_thenTheyAreDifferent() throws IOException {
Map<String, Object> firstFlatMap = FlattenUtils.flatten(JsonUtils.jsonFileToMap("src/test/resources/first.json"));
Map<String, Object> secondFlatMap = FlattenUtils.flatten(JsonUtils.jsonFileToMap("src/test/resources/second.json"));
MapDifference<String, Object> difference = Maps.difference(firstFlatMap, secondFlatMap);
difference.entriesDiffering().forEach((key, value) -> {
System.out.println(key + ": " + value.leftValue() + " - " + value.rightValue());
});
assertThat(difference.areEqual()).isFalse();
}
再次,我們將打印出不同的鍵和值。這會導致以下輸出:
cars: [Ford, BMW] - [Ford, Audi]
children[1].age: 3 - 10
children[1].name: Alex - Cathy
address.street: Second Street - Main Street
children[0].name: Sara - Peter
4. 結論
在本文中,我們探討了如何在 Java 中比較兩個 JSON 文檔。我們研究了將 JSON 文檔轉換為 Map 的不同方法,然後使用 Guava 的 Maps.difference() 方法進行比較。我們還研究瞭如何扁平化 Map 以進行嵌套 Map 的比較。