將 JSON 文檔作為地圖讀取並進行比較
1. 概述
在本教程中,我們將了解將 JSON 文檔讀取為Map
的不同方法並進行比較。我們還將研究找出兩個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
數組的值不同 -
address
對像中street
鍵的值不同 -
children
數組有多個差異
2.1.使用傑克遜
Jackson 是一個用於 JSON 操作的流行庫。我們可以使用 Jackson 將 JSON 轉換為Map.
讓我們首先添加Jackson 依賴項:
<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.10.1</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 的Map.difference()
Guava 提供了Maps.difference()
方法,可用於比較兩個Map
。為了利用它,我們將Guava依賴項添加到我們的項目中:
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>32.1.2-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
。
讓我們看看如何比較上面的嵌套Maps
。我們使用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}]
我們可以看到,這表明cars, address,
和children
字段是不同的,並且列出了差異。但是,這並沒有顯示哪些嵌套字段導致了這些差異。例如,它沒有指出address
對像中的street
字段是不同的。
3.2.展Map
為了精確指出嵌套Map
之間的差異,我們將展平Map
,以便每個鍵都是到值的路徑。例如, address
對像中的street
鍵將被展平為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.測試
現在我們已經展平了Map
,讓我們看看如何使用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
。
與往常一樣,本文的代碼可以在 GitHub 上獲取。