1. 概述
本教程將探討如何將 Jackson 的原始數據類型 <em >JsonNode</em> 轉換為類型為 Java 集合的不同方法。雖然我們可以使用 <em >JsonNode</em> 本身讀取 JSON 數據,但將其轉換為 Java 集合可能會有所幫助。Java 集合相對於原始 JSON 數據,例如類型安全、更快的處理速度以及更多類型特定的操作,都具有優勢。
2. 示例設置
在我們的代碼示例中,我們將探討如何將 <em >JsonNode</em >> 轉換為 <em >List</em >> 或 <em >Map</em >> 對象列表。讓我們設置示例的基礎構建塊。
2.1. 依賴
首先,我們將 Jackson Core 依賴添加到 pom.xml 文件中:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.17.0</version>
</dependency>
2.2. JSON 數據
接下來,讓我們為我們的用例定義一個 JSON:
{
"persons": [
{
"name": "John",
"age": 30
},
{
"name": "Alice",
"age": 25
}
],
"idToPerson" : {
"1234": {
"name": "John",
"age": 30
},
"1235": {
"name": "Alice",
"age": 25
}
}
}
在上述JSON中,我們有一個JSON數組persons和一個JSON對象idToPerson。我們將探討如何將它們轉換為Java集合。
2.3. DTO
讓我們定義一個 Person 類,以便在我們的示例中使用它:
public class Person {
private String name;
private int age;
// constructors/getters/setters
}
2.4. 將 JSON 字符串轉換為 JsonNode
如果我們想從整個 JSON 對象中讀取對象,可以使用 Jackson 的 ObjectMapper 類來實現:
JsonNode rootNode = new ObjectMapper().readTree(jsonString);
JsonNode childNode = rootNode.get("persons");
要將整個JSON轉換為一個JsonNode對象,我們使用readTree()方法。然後,我們使用get()方法遍歷JsonNode對象,該方法返回具有指定名稱的嵌套對象。
3. 手動轉換
在檢查庫方法之前,讓我們看看如何手動將一個 JsonNode 轉換為一個集合。
3.1. 手動將 JsonNode 轉換為 List
要將 JsonNode 轉換為 List,我們可以逐個條目地遍歷它,並創建一個包含該條目的 List 對象:
List<Person> manualJsonNodeToList(JsonNode personsNode) {
List<Person> people = new ArrayList<>();
for (JsonNode node : personsNode) {
Person person = new Person(node.get("name").asText(), node.get("age").asInt());
people.add(person);
}
return people;
}
在這裏,我們使用循環遍歷輸入節點的所有子節點。這隻有當我們的輸入節點是一個數組時才可能實現。
對於每個節點,我們創建一個Person對象並將其添加到列表中。我們使用get(fieldName)方法從節點中獲取name和age。JsonNode提供了各種方法,可以將返回的值轉換為原始的Java類型。在這裏,asText()和asInt()方法將值轉換為String和int, respectively。
3.2. 手動將 JsonNode 轉換為 Map
讓我們來看一個類似的轉換,用於將 Map 轉換為:
Map<String, Person> manualJsonNodeToMap(JsonNode idToPersonsNode) {
Map<String, Person> mapOfIdToPerson = new HashMap<>();
idToPersonsNode.fields()
.forEachRemaining(node -> mapOfIdToPerson.put(node.getKey(),
new Person(node.getValue().get("name").asText(), node.getValue().get("age").asInt())));
return mapOfIdToPerson;
}
在這裏,我們使用 fields() 方法來遍歷 map 的條目。它返回一個 Iterator<Map.Entry<String, JsonNode>> 對象,我們可以進一步處理它。接下來,我們讀取每個條目並將其放入我們的 Map 中。
4. 使用 Jackson 的 readValue() 和 convertValue()
Jackson 提供了多種將 JsonNode 轉換為 Java 對象的方法。下面我們來看其中兩種。
4.1. 使用 readValue() 方法
readValue() 方法可用於通過 TypeReference 將數據轉換為 List 或 Map。
List<Person> readValueJsonNodeToList(JsonNode personsNode) throws IOException {
TypeReference<List<Person>> typeReferenceList = new TypeReference<List<Person>>() {};
return new ObjectMapper().readValue(personsNode.traverse(), typeReferenceList);
}
Map<String, Person> readValueJsonNodeToMap(JsonNode idToPersonsNode) throws IOException {
TypeReference<Map<String, Person>> typeReferenceMap = new TypeReference<Map<String, Person>>() {};
return new ObjectMapper().readValue(idToPersonsNode.traverse(), typeReferenceMap);
}
首先,我們通過傳遞所需轉換的精確類型來創建一個 TypeReference 對象。然後,我們調用 readValue() 方法,其中 JsonParser 由 jsonNode.traverse() 提供。利用解析器,它將節點反序列化為根據我們提供的 TypeReference 轉換為列表或映射。
4.2. 使用 convertValue() 方法
類似於地,我們可以使用 `convertValue()` 方法:
List<Person> convertValueJsonNodeToList(JsonNode personsNode) {
TypeReference<List<Person>> typeReferenceList = new TypeReference<List<Person>>() {};
return new ObjectMapper().convertValue(personsNode, typeReferenceList);
}
Map<String, Person> convertValueJsonNodeToMap(JsonNode idToPersonsNode) {
TypeReference<Map<String, Person>> typeReferenceMap = new TypeReference<Map<String, Person>>() {};
return new ObjectMapper().convertValue(idToPersonsNode, typeReferenceMap);
}
The convertValue() 方法的工作原理是首先對輸入對象進行序列化,然後將其反序列化為所需的類型。因此,它可更靈活地用於將一個對象轉換為另一個對象。例如,我們也可以使用它從 Java 對象到 JsonNode 對象進行反向比較。
5. 自定義反序列器
我們可以提供自定義反序列器來執行轉換。下面我們來看如何定義一個:
public class CustomPersonListDeserializer extends JsonDeserializer<List<Person>> {
@Override
public List<Person> deserialize(com.fasterxml.jackson.core.JsonParser p,
com.fasterxml.jackson.databind.DeserializationContext ctxt) throws IOException {
ObjectMapper objectMapper = (ObjectMapper) p.getCodec();
List<Person> personList = new ArrayList<>();
JsonNode personsNode = objectMapper.readTree(p);
for (JsonNode node : personsNode) {
personList.add(objectMapper.readValue(node.traverse(), Person.class));
}
return personList;
}
}
讓我們來研究一下代碼中的幾個重要部分:
- 首先,該類繼承了 Jackson 的 JsonDeserializer。
- 然後,我們覆蓋了 deserialize() 方法並提供我們的實現。
- 在實現中,我們從 JsonParser 對象中獲取 ObjectMapper。
- objectMapper.readTree() 將解析器表示的所有樹轉換為 JsonNode 實例。
- 最後,類似於手動轉換,我們通過循環遍歷節點,將 JSON 數組中的每個節點轉換為 Person 對象。
解器的工作方式與其它方法類似,但它提供了分層考慮。 此外,自定義解器提供了靈活性,因為我們可以輕鬆地在調用代碼中切換解器。
在測試代碼時,我們將研究如何使用解器,將在下一部分中進行研究。
6. 測試
現在我們已經準備好不同的方法,接下來我們將編寫測試以驗證它們。
6.1. 搭建環境
讓我們設置測試類:
public class JsonNodeToCollectionUnitTest {
public static String jsonString = "{\"persons\":[{\"name\":\"John\",\"age\":30},{\"name\":\"Alice\",\"age\":25}],\"idToPerson\":{\"1234\":{\"name\":\"John\",\"age\":30},\"1235\":{\"name\":\"Alice\",\"age\":25}}}";
static JsonNode completeJson;
static JsonNode personsNode;
static JsonNode idToPersonNode;
@BeforeAll
static void setup() throws JsonProcessingException {
completeJson = new ObjectMapper().readTree(jsonString);
personsNode = completeJson.get("persons");
idToPersonNode = completeJson.get("idToPerson");
}
}
在這裏,我們定義一個 JSON 字符串,可作為測試輸入使用。然後,我們定義一個 setup() 方法,在所有測試執行之前執行。它設置我們的輸入 JsonNode 對象。
6.2. 測試轉換方法
接下來,讓我們測試我們的轉換方法:
@Test
void givenJsonNode_whenConvertingToList_thenFieldsAreCorrect() throws IOException {
List<Person> personList1 = JsonNodeConversionUtil.manualJsonNodeToList(personsNode);
List<Person> personList2 = JsonNodeConversionUtil.readValueJsonNodeToList(personsNode);
List<Person> personList3 = JsonNodeConversionUtil.convertValueJsonNodeToList(personsNode);
validateList(personList1);
validateList(personList2);
validateList(personList3);
}
private void validateList(List<Person> personList) {
assertEquals(2, personList.size());
Person person1 = personList.get(0);
assertEquals("John", person1.getName());
assertEquals(30, person1.getAge());
Person person2 = personList.get(1);
assertEquals("Alice", person2.getName());
assertEquals(25, person2.getAge());
}
在這裏,我們調用每個方法並驗證返回列表的內容是否符合預期。
接下來,我們添加一個類似的測試,用於驗證映射轉換:
@Test
void givenJsonNode_whenConvertingToMap_thenFieldsAreCorrect() throws IOException {
Map<String, Person> personMap1 = JsonNodeConversionUtil.manualJsonNodeToMap(idToPersonNode);
Map<String, Person> personMap2 = JsonNodeConversionUtil.readValueJsonNodeToMap(idToPersonNode);
Map<String, Person> personMap3 = JsonNodeConversionUtil.convertValueJsonNodeToMap(idToPersonNode);
validateMapOfPersons(personMap1);
validateMapOfPersons(personMap2);
validateMapOfPersons(personMap3);
}
private void validateMapOfPersons(Map<String, Person> map) {
assertEquals(2, map.size());
Person person1 = map.get("1234");
assertEquals("John", person1.getName());
assertEquals(30, person1.getAge());
Person person2 = map.get("1235");
assertEquals("Alice", person2.getName());
assertEquals(25, person2.getAge());
}
6.3. 自定義解器測試
讓我們看看如何使用自定義解器,然後比較結果:
@Test
void givenJsonNode_whenConvertingToListWithCustomDeserializer_thenFieldsAreCorrect() throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(List.class, new CustomPersonListDeserializer());
objectMapper.registerModule(module);
List<Person> personList = objectMapper.convertValue(personsNode, new TypeReference<List<Person>>() {
});
validateList(personList);
}
在這裏,我們首先使用 SimpleModule 類將 CustomPersonListDeserializer 包裹在一個模塊中。然後,我們將該模塊註冊到 ObjectMapper 實例中。通過這樣做,我們指示 ObjectMapper 在需要反序列化 List 時使用 CustomPersonListDeserializer。
當我們調用 convertValue() 方法時,反序列器將被用於返回輸出列表。
7. 結論
在本文中,我們探討了如何將 Jackson 的 <em >JsonNode</em> 轉換為帶類型 Java 集合,例如 <em >List</em> 和 <em >Map</em>。我們討論了多種執行此操作的方法,並驗證了它們都提供相同的結果。
對於小規模的轉換,我們可以使用手動方法。隨着 JSON 大小增加,使用 Jackson 庫中的方法會更好。如果需要更復雜的轉換,則最好提供自定義的解序列化器。