1. 概述
本教程將介紹如何解決 Jackson 異常 `JsonMappingException: Can not deserialize instance of java.util.HashMap out of START_ARRAY token》。
首先,我們將闡明該異常的根本原因。然後,我們將通過一個實際示例演示如何重現該異常,最後,我們將提供解決方案。
2. 理解異常
Jackson 通過拋出 <em>JsonMappingException</em> 來指示在反序列化 JSON 字符串時發生的映射錯誤。 此外,消息 <em>“Can not deserialize instance of java.util.HashMap out of START_ARRAY token”</em> 表明預期的數據結構與實際的 JSON 字符串之間存在不匹配。
這裏不匹配的原因在於 Jackson 期望一個 <em>List</em> 而不是一個 <em>HashMap</em>。 反序列化過程無法將指定的 JSON 數組轉換為 <em>HashMap</em>。 因此,拋出了異常。
3. 實際示例
現在我們瞭解了導致 Jackson 失敗的原因(即 JsonMappingException),讓我們通過一個實際示例來演示如何重現它。
為了簡化問題,假設我們有一個 JSON 數組,其中每個元素代表一個人,由名字和姓氏定義:
[
{
"firstName":"Abderrahim",
"lastName":"Azhrioun"
},
{
"firstName":"Nicole",
"lastName":"Smith"
}
]現在,嘗試直接將 JSON 數組反序列化為 Map 將會導致 JsonMappingException:
@Test
public void givenJsonArray_whenDeserializingToMap_thenThrowException() {
final String json = "[{\"firstName\":\"Abderrahim\",\"lastName\":\"Azhrioun\"}, {\"firstName\":\"Nicole\",\"lastName\":\"Smith\"}]";
final ObjectMapper mapper = new ObjectMapper();
Exception exception = assertThrows(JsonMappingException.class, () -> mapper.readValue(json, HashMap.class));
assertTrue(exception.getMessage()
.contains(
"Cannot deserialize value of type `java.util.HashMap<java.lang.Object,java.lang.Object>` from Array value (token `JsonToken.START_ARRAY`)"));}如我們所見,Jackson 由於遇到 ObjectMapper 不知道如何直接將給定的 JSON 數組反序列化為 HashMap。
4. 解決方案
默認情況下,Jackson 期望在反序列化 JSON 數組時得到一個 List。因此,最直接的解決方案是使用一個包含 Map 的 List,而不是一個單獨的 Map。
讓我們來看一個示例:
@Test
public void givenJsonArray_whenDeserializingToListOfMap_thenConvert() throws JsonProcessingException {
final List<Map<String, String>> expectedListOfMaps = Arrays.asList(Map.of("firstName", "Abderrahim", "lastName", "Azhrioun"),
Map.of("firstName", "Nicole", "lastName", "Smith"));
final String json = "[{\"firstName\":\"Abderrahim\",\"lastName\":\"Azhrioun\"}, {\"firstName\":\"Nicole\",\"lastName\":\"Smith\"}]";
final ObjectMapper mapper = new ObjectMapper();
List<Map<String, String>> personList = mapper.readValue(json, new TypeReference<>() {});
assertThat(expectedListOfMaps).isEqualTo(personList);
}如上所示,我們使用 TypeReference 指示 Jackson 將 JSON 數組反序列化為包含 Map<String, String> 的 List。
或者,我們可以創建一個自定義類來表示每個 JSON 元素。 例如,讓我們考慮 Person 類:
public class Person {
private String firstName;
private String lastName;
// default constructor, full parameterized constructor, getters and setters
}基本思路是使用 Person 類而不是 Map<String, String>。 這樣,每個 JSON 數組的元素都會映射到一個 Person 對象。 讓我們用另一個測試用例來確認這一點:
@Test
public void givenJsonArray_whenDeserializingToListOfCustomObjects_thenConvert() throws JsonProcessingException {
final List<Person> expectedPersonList = Arrays.asList(new Person("Abderrahim", "Azhrioun"), new Person("Nicole", "Smith"));
final String json = "[{\"firstName\":\"Abderrahim\",\"lastName\":\"Azhrioun\"}, {\"firstName\":\"Nicole\",\"lastName\":\"Smith\"}]";
final ObjectMapper mapper = new ObjectMapper();
List<Person> personList = mapper.readValue(json, new TypeReference<>() {});
assertThat(expectedPersonList).usingRecursiveComparison()
.isEqualTo(personList);
}在這裏,我們告訴 Jackson 返回一個 List 包含 Person 對象,而不是一個 List 包含 Map<String, String> 對象。
5. 結論
在本文中,我們解釋了 Jackson 異常 <em >JsonMappingException: Can not deserialize instance of java.util.HashMap out of START_ARRAY token</em> 的主要原因。 在過程中,我們演示瞭如何重現該異常以及如何解決它。