Jackson: java.util.LinkedHashMap cannot be cast to X

简介: Jackson: java.util.LinkedHashMap cannot be cast to X

本文翻译自:https://www.baeldung.com/jackson-linkedhashmap-cannot-be-cast

1.概述:

Jackson是一个广泛使用的 Java 库,它允许我们方便地序列化/反序列化 JSON 或 XML。

有时,当我们尝试将 JSON 或 XML 反序列化为对象集合时,可能会遇到“ java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to X ”。

在本教程中,我们将讨论为什么会发生上述异常以及如何解决该问题。

2.理解问题

让我们创建一个简单的 Java 应用程序来重现此异常,以了解异常何时发生。

2.1 创建 POJO 类

让我们从一个简单的 POJO 类开始:

public class Book {
    private Integer bookId;
    private String title;
    private String author;
    //getters, setters, constructors, equals and hashcode omitted
}

现在,假设我们的 books.json文件由一个包含三本书的 JSON 数组组成:

[ {
    "bookId" : 1,
    "title" : "A Song of Ice and Fire",
    "author" : "George R. R. Martin"
}, {
    "bookId" : 2,
    "title" : "The Hitchhiker's Guide to the Galaxy",
    "author" : "Douglas Adams"
}, {
    "bookId" : 3,
    "title" : "Hackers And Painters",
    "author" : "Paul Graham"
} ]

接下来,我们将看看当我们尝试将 JSON 示例反序列化为List<Book>时会发生什么:

2.2. 将 JSON 反序列化为List<Book>

让我们看看是否可以通过将此 JSON 文件反序列化为List<Book>对象并从中读取元素来重现类转换问题:

@Test
void givenJsonString_whenDeserializingToList_thenThrowingClassCastException() 
  throws JsonProcessingException {
    String jsonString = readFile("/to-java-collection/books.json");
    List<Book> bookList = objectMapper.readValue(jsonString, ArrayList.class);
    assertThat(bookList).size().isEqualTo(3);
    assertThatExceptionOfType(ClassCastException.class)
      .isThrownBy(() -> bookList.get(0).getBookId())
      .withMessageMatching(".*java.util.LinkedHashMap cannot be cast to .*com.baeldung.jackson.tocollection.Book.*");
}

我们使用AssertJ库来验证当我们调用bookList.get(0).getBookId()时是否引发了预期的异常,并且它的消息与我们的问题陈述中记录的消息相匹配

测试通过,这意味着我们已经成功地重现了问题。

2.3. 为什么抛出异常

现在,如果我们仔细查看异常消息:“ class java.util.LinkedHashMap cannot be cast to class ... Book ”,可能会出现几个问题。

我们已经用List<Book>类型 声明了变量bookList,但是为什么 Jackson 尝试将LinkedHashMap类型转换为我们的Book类?此外,LinkedHashMap是从哪里来的?

首先,确实我们用List<Book>类型声明了 bookList 。但是,当我们调用objectMapper.readValue()方法时,我们将ArrayList.class作为Class对象传递 。因此,Jackson 会将 JSON 内容反序列化为ArrayList对象,但它不知道ArrayList对象中应该包含什么类型的元素。

其次,当 Jackson 尝试反序列化 JSON 中的对象,但没有给出目标类型信息时,它将使用默认类型:LinkedHashMap。换句话说,在反序列化之后,我们会得到一个ArrayList<LinkedHashMap>对象。在 Map中,键是属性的名称——例如,“ bookId ”、“ title ”等。这些值是相应属性的值:

 

 

现在我们了解了问题的原因,让我们讨论如何解决它。

3.将TypeReference传递给objectMapper.readValue()

为了解决这个问题,我们需要让Jackson知道元素的类型。但是,编译器不允许我们执行objectMapper.readValue(jsonString, ArrayList<Book>.class) 之类的操作。

相反,我们可以将TypeReference对象传递给objectMapper.readValue(String content, TypeReference valueTypeRef)方法。在这种情况下,我们只需要传递new TypeReference<List<Book>>() {}作为第二个参数:

@Test
void givenJsonString_whenDeserializingWithTypeReference_thenGetExpectedList() 
  throws JsonProcessingException {
    String jsonString = readFile("/to-java-collection/books.json");
    List<Book> bookList = objectMapper.readValue(jsonString, new TypeReference<List<Book>>() {});
    assertThat(bookList.get(0)).isInstanceOf(Book.class);
    assertThat(bookList).isEqualTo(expectedBookList);
}

如果我们运行测试,它成功通过。因此,传递一个TypeReference对象可以解决我们的问题。

4.将JavaType传递给objectMapper.readValue()

在上一节中,我们讨论了传递一个Class对象或TypeReference对象作为第二个参数来调用objectMapper.readValue()方法。

objectMapper.readValue ()方法仍然接受JavaType对象作为第二个参数。JavaType 是类型标记类的基类。它将被反序列化器使用,以便反序列化器在反序列化期间知道目标类型是什么。

我们可以通过 TypeFactory 实例构造一个JavaType对象 ,我们可以从objectMapper.getTypeFactory()中获取TypeFactory对象。

让我们回到我们的图书示例。在这个例子中,我们想要的目标类型是ArrayList<Book>。因此,我们可以根据这个要求构造一个CollectionType :

objectMapper.getTypeFactory().constructCollectionType(ArrayList.class, Book.class);

现在,让我们编写一个单元测试,看看将JavaType传递给 readValue() 方法是否可以解决我们的问题:

@Test
void givenJsonString_whenDeserializingWithJavaType_thenGetExpectedList() 
  throws JsonProcessingException {
    String jsonString = readFile("/to-java-collection/books.json");
    CollectionType listType = 
      objectMapper.getTypeFactory().constructCollectionType(ArrayList.class, Book.class);
    List<Book> bookList = objectMapper.readValue(jsonString, listType);
    assertThat(bookList.get(0)).isInstanceOf(Book.class);
    assertThat(bookList).isEqualTo(expectedBookList);
} 

5.使用JsonNode对象和objectMapper.convertValue()方法

我们已经看到了将TypeReference或JavaType对象传递给objectMapper.readValue()方法的解决方案。

或者,我们可以在 Jackson 中使用树模型节点, 然后通过调用objectMapper.convertValue()方法将JsonNode对象转换为所需的类型。

同样,我们可以将TypeReference或JavaType的对象传递给objectMapper.convertValue()方法。

让我们看看每种方法的实际效果。

首先,让我们使用TypeReference 对象和objectMapper.convertValue()方法创建一个测试方法:

@Test
void givenJsonString_whenDeserializingWithConvertValueAndTypeReference_thenGetExpectedList() 
  throws JsonProcessingException {
    String jsonString = readFile("/to-java-collection/books.json");
    JsonNode jsonNode = objectMapper.readTree(jsonString);
    List<Book> bookList = objectMapper.convertValue(jsonNode, new TypeReference<List<Book>>() {});
    assertThat(bookList.get(0)).isInstanceOf(Book.class);
    assertThat(bookList).isEqualTo(expectedBookList);
}

现在,让我们看看当我们将JavaType对象传递给objectMapper.convertValue()方法时会发生什么:

@Test
void givenJsonString_whenDeserializingWithConvertValueAndJavaType_thenGetExpectedList() 
  throws JsonProcessingException {
    String jsonString = readFile("/to-java-collection/books.json");
    JsonNode jsonNode = objectMapper.readTree(jsonString);
    List<Book> bookList = objectMapper.convertValue(jsonNode, 
      objectMapper.getTypeFactory().constructCollectionType(ArrayList.class, Book.class));
    assertThat(bookList.get(0)).isInstanceOf(Book.class);
    assertThat(bookList).isEqualTo(expectedBookList);
}

如果我们运行这两个测试,它们都会通过。因此,使用objectMapper.convertValue()方法是解决问题的另一种方法。

6.创建通用反序列化方法

到目前为止,我们已经解决了在将 JSON 数组反序列化为 Java 集合时如何解决类转换问题。在现实世界中,我们可能希望创建一个通用方法来处理不同的元素类型。

现在对我们来说这不会是一项艰巨的工作。我们可以 在调用objectMapper.readValue()方法时传递一个JavaType对象:

public static <T> List<T> jsonArrayToList(String json, Class<T> elementClass) throws IOException {
    ObjectMapper objectMapper = new ObjectMapper();
    CollectionType listType = 
      objectMapper.getTypeFactory().constructCollectionType(ArrayList.class, elementClass);
    return objectMapper.readValue(json, listType);
}

接下来,让我们创建一个单元测试方法来验证它是否按预期工作:

public static <T> List<T> jsonArrayToList(String json, Class<T> elementClass) throws IOException {
    return new ObjectMapper().readValue(json, new TypeReference<List<T>>() {});
}

如果我们运行它,测试将通过。

为什么不使用TypeReference方法来构建泛型方法,因为它看起来更紧凑?

现在,让我们创建一个通用实用程序方法并将相应的TypeReference对象传递给objectMapper.readValue()方法:

public static <T> List<T> jsonArrayToList(String json, Class<T> elementClass) throws IOException {
    return new ObjectMapper().readValue(json, new TypeReference<List<T>>() {});
}

该方法看起来很简单。如果我们再次运行测试方法,我们将得到:

java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class com.baeldung...Book ...

糟糕,发生异常!

我们已经将一个TypeReference对象传递给 readValue()方法,并且我们之前已经看到这种方法可以解决类转换问题。那么,为什么在这种情况下我们会看到相同的异常?

这是因为我们的方法是通用的。类型参数T不能在运行时具体化,即使我们传递一个带有类型参数T的TypeReference实例。

 

本篇文章如有帮助到您,请给「翎野君」点个赞,感谢您的支持。


目录
相关文章
|
2月前
|
Java 数据库连接 mybatis
成功解决:java.lang.Integer cannot be cast to java.lang.Long
这篇文章讨论了Java中常见的类型转换错误,包括Integer转Long、Integer转String以及在MyBatis中Map接收查询结果时的类型不匹配问题,并提供了相应的解决方法。
|
8天前
|
Oracle Java 关系型数据库
Linux下JDK环境的配置及 bash: /usr/local/java/bin/java: cannot execute binary file: exec format error问题的解决
如果遇到"exec format error"问题,文章建议先检查Linux操作系统是32位还是64位,并确保安装了与系统匹配的JDK版本。如果系统是64位的,但出现了错误,可能是因为下载了错误的JDK版本。文章提供了一个链接,指向Oracle官网上的JDK 17 Linux版本下载页面,并附有截图说明。
Linux下JDK环境的配置及 bash: /usr/local/java/bin/java: cannot execute binary file: exec format error问题的解决
|
2月前
|
Java 开发工具
开发工具系类 之 Cannot determine path to ‘tools.jar‘ library for 17 (D:/Program Files/Java/jdk-17.0.9)
这篇文章讲述了作者在升级JDK至17版本后遇到IDEA无法识别`tools.jar`的问题,并提供了两种解决方法:升级IDEA版本或降低JDK版本,并提供了相关版本的IDEA兼容性信息。
开发工具系类 之 Cannot determine path to ‘tools.jar‘ library for 17 (D:/Program Files/Java/jdk-17.0.9)
|
2月前
|
前端开发 Java
成功解决:java.lang.String cannot be cast to java.lang.Integer
这篇文章记录了作者在使用Axios二次封装时遇到的一个Java类型转换问题,即前端传递的字符串参数不能直接转换为Integer类型,文章提供了正确的转换方法来解决这个问题。
成功解决:java.lang.String cannot be cast to java.lang.Integer
|
2月前
|
JSON 前端开发 fastjson
成功解决:java.util.LinkedHashMap cannot be cast to com.zyz.bookshopmanage.pojo.GoodsInfo
这篇文章讲述了在Java后端开发中遇到的类型转换错误,即无法将`java.util.LinkedHashMap`转换为`com.zyz.bookshopmanage.pojo.GoodsInfo`对象的问题。文章提供了解决这个问题的两种方法:一是将对象转换为JSON字符串再反序列化为对象,二是通过在项目的pom文件中引入fastjson库来简化转换过程。最后,文章展示了成功转换对象的代码示例。
成功解决:java.util.LinkedHashMap cannot be cast to com.zyz.bookshopmanage.pojo.GoodsInfo
|
2月前
|
JSON 前端开发 JavaScript
JSON parse error: Cannot deserialize value of type `java.lang.Integer` from Boolean value
这篇文章讨论了前端Vue应用向后端Spring Boot服务传输数据时发生的类型不匹配问题,即后端期望接收的字段类型为`int`,而前端实际传输的类型为`Boolean`,导致无法反序列化的问题,并提供了问题的诊断和解决方案。
JSON parse error: Cannot deserialize value of type `java.lang.Integer` from Boolean value
|
2月前
|
Android开发
Cannot create android app from an archive...containing both DEX and Java-bytecode content
Cannot create android app from an archive...containing both DEX and Java-bytecode content
23 2
|
2月前
|
开发工具 数据安全/隐私保护
【Azure Developer】使用MSAL4J 与 ADAL4J 的SDK时候,遇见了类型冲突问题 "java.util.Collections$SingletonList cannot be cast to java.lang.String"
【Azure Developer】使用MSAL4J 与 ADAL4J 的SDK时候,遇见了类型冲突问题 "java.util.Collections$SingletonList cannot be cast to java.lang.String"
|
6天前
|
安全 Java 调度
Java编程时多线程操作单核服务器可以不加锁吗?
Java编程时多线程操作单核服务器可以不加锁吗?
21 2
|
10天前
|
存储 缓存 Java
java线程内存模型底层实现原理
java线程内存模型底层实现原理
java线程内存模型底层实现原理
下一篇
无影云桌面