JsonMappingException:Can not deserialize instance of java.util.HashMap out of START_ARRAY token
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
)和姓(lastName
)定义的人:
[
{
"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
。因此,最直接的解决方案是使用 List<Map>
而不是单个 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<Map<String, String>>
。
或者,我们可以创建一个自定义类来表示每个 JSON 元素。
定义 Person
类:
public class Person {
private String firstName;
private String lastName;
//忽略构造函数和 Getter / Setter 方法
}
这里的基本思想是使用 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 返回 Person
对象的 List
,而不是 Map<String, String>
的 List
。
5、总结
本文介绍了 Jackson 抛出 “JsonMappingException: Can not deserialize instance of java.util.HashMap out of START_ARRAY token” 异常的主要原因,以及解决办法。
Ref:https://www.baeldung.com/java-fix-the-jsonmappingexception-can-not-deserialize-instance-of-java-util-hashmap-out-of-start_array-token