1. 概述
在本簡短教程中,我們將探討如何解決 Jackson 異常 JsonMappingException: Can not deserialize instance of java.util.HashMap out of START_ARRAY token。
首先,我們將闡明異常的根本原因。然後,我們將通過一個實際示例演示如何重現它,最後,我們將如何解決它。
2. 理解異常
簡而言之,Jackson 在反序列化 JSON 字符串時,會拋出 JsonMappingException 以指示映射錯誤。 此外,消息 “Can not deserialize instance of java.util.HashMap out of START_ARRAY token” 表明預期數據結構與實際 JSON 字符串之間存在不匹配。
此處不匹配的原因是 Jackson 期望一個 List 而不是一個 HashMap。 反序列化過程不知道如何將指定的 JSON 數組轉換為 HashMap。 因此,拋出了異常。
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 由於 “Cannot deserialize value of type HashMap from Array value (token `JsonToken.START_ARRAY`)” 而失敗,因為 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 數組反序列化為 List,該 List 包含 Map<String, String> 對象。
或者,我們可以創建一個自定義類來表示每個 JSON 元素。
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,該 List 包含 Person 對象,而不是 List,該 List 包含 Map<String, String> 對象。
5. 結論
在本文中,我們解釋了 Jackson 異常 JsonMappingException: Can not deserialize instance of java.util.HashMap out of START_ARRAY token 的主要原因。 在過程中,我們展示瞭如何重現該異常以及如何解決它。