拨开荷叶行,寻梦已然成。仙女莲花里,翩翩白鹭情。
IMG-LOGO
主页 文章列表 Jackson:java.util.LinkedHashMap无法转换为X

Jackson:java.util.LinkedHashMap无法转换为X

白鹭 - 2021-11-24 1424 0 0

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 RR 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 List<Book>.但是,当我们调用objectMapper.readValue()方法时,我们将ArrayList.class传递为Class对象。因此,Jackson会将JSON内容反序列化为ArrayList对象,但不知道ArrayList像中应包含哪种类型的元素。

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

杰克逊:java.util.LinkedHashMap无法转换为X

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

3.传递TypeReferenceobjectMapper.readValue()

要解决该问题,我们需要以某种方式让Jackson知道元素的类型。但是,编译器不允许我们执行诸如objectMapper.readValue(jsonString, ArrayList<Book>.class)

相反,我们可以将TypeReferenceobjectMapper.readValue(String content, TypeReference valueTypeRef)传递给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.传JavaTypeobjectMapper.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()方法

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

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

类似地,我们可以将TypeReferenceJavaTypeobjectMapper.convertValue() JavaTypeobjectMapper.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);

 }

现在,让我们看看将JavaTypeobjectMapper.convertValue()传递给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);

 }

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

@Test

 void givenJsonString_whenCalljsonArrayToList_thenGetExpectedList() throws IOException {

 String jsonString = readFile("/to-java-collection/books.json");

 List<Book> bookList = JsonToCollectionUtil.jsonArrayToList(jsonString, Book.class);

 assertThat(bookList.get(0)).isInstanceOf(Book.class);

 assertThat(bookList).isEqualTo(expectedBookList);

 }

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

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

现在,让我们创建一个通用实用程序方法,并将相应的TypeReferenceobjectMapper.readValue()传递给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不能在运行时被具体化,即使我们通过TypeReference实例与类型参数T.

7.用Jackson进行XML反序列化

除了JSON序列化/反序列化之外,Jackson库还可用于对XML进行序列化/反序列化。

让我们举一个简单的例子,检查将XML反序列化为Java集合时是否可能发生相同的问题。

首先,让我们创建一个XML文件books.xml

<ArrayList>

 <item>

 <bookId>1</bookId>

 <title>A Song of Ice and Fire</title>

 <author>George RR Martin</author>

 </item>

 <item>

 <bookId>2</bookId>

 <title>The Hitchhiker's Guide to the Galaxy</title>

 <author>Douglas Adams</author>

 </item>

 <item>

 <bookId>3</bookId>

 <title>Hackers And Painters</title>

 <author>Paul Graham</author>

 </item>

 </ArrayList>

接下来,就像处理JSON文件一样,我们创建另一个单元测试方法来验证是否会抛出类转换异常:

@Test

 void givenXml_whenDeserializingToList_thenThrowingClassCastException()

 throws JsonProcessingException {

 String xml = readFile("/to-java-collection/books.xml");

 List<Book> bookList = xmlMapper.readValue(xml, 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.*");

 }

如果我们进行测试,我们的测试将通过。也就是说,在XML反序列化中也会发生相同的问题。

但是,如果我们知道如何解决JSON反序列化,则将其修复为XML反序列化非常简单。

由于XmlMapperObjectMapper,的子类ObjectMapper,因此我们为JSON反序列化解决的所有解决方案也适用于XML反序列化。

例如,我们可以将TypeReferencexmlMapper.readValue()传递给xmlMapper.readValue()方法来解决该问题:

@Test

 void givenXml_whenDeserializingWithTypeReference_thenGetExpectedList()

 throws JsonProcessingException {

 String xml = readFile("/to-java-collection/books.xml");

 List<Book> bookList = xmlMapper.readValue(xml, new TypeReference<List<Book>>() {});

 assertThat(bookList.get(0)).isInstanceOf(Book.class);

 assertThat(bookList).isEqualTo(expectedBookList);

 }

8.结论

在本文中,我们讨论了当我们使用杰克逊反序列化JSON或XML时为什么会出现“ java.util.LinkedHashMap cannot be cast to X ”异常的原因。

之后,我们通过示例解决了解决问题的不同方法。

与往常一样,本文中的代码都可以在GitHub上找到

标签:

0 评论

发表评论

您的电子邮件地址不会被公开。 必填的字段已做标记 *