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