1. Overview
1.概述
Jackson is a widely used Java library that allows us to conveniently serialize/deserialize JSON or XML.
Jackson是一个广泛使用的Java库,它允许我们方便地对JSON或XML进行序列化/反序列化。
Sometimes, we may encounter java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to X when we try to deserialize JSON or XML into a collection of objects.
有时,当我们试图将JSON或XML反序列化为一个对象集合时,我们可能会遇到java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to X。
In this tutorial, we’ll discuss why this exception can occur and how to solve the problem.
在本教程中,我们将讨论为什么会出现这种异常以及如何解决这个问题。
2. Understanding the Problem
2.了解问题
Let’s create a simple Java application to reproduce this exception to understand when the exception will occur.
让我们创建一个简单的Java应用程序来重现这个异常,以了解这个异常何时发生。
2.1. Creating a POJO Class
2.1.创建一个POJO类
Let’s start with a simple POJO class:
让我们从一个简单的POJO类开始。
public class Book {
private Integer bookId;
private String title;
private String author;
//getters, setters, constructors, equals and hashcode omitted
}
Suppose we have the books.json file consisting of a JSON array that contains three books:
假设我们有一个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"
} ]
Next, we’ll see what happens when we try to deserialize our JSON example to List<Book>.
接下来,我们将看到当我们试图将我们的JSON例子反序列化为List<Book>时会发生什么。
2.2. Deserializing JSON to List<Book>
2.2.将JSON反序列化为List<Book>
Let’s see if we can reproduce the class casting problem by deserializing this JSON file to a List<Book> object and reading the elements from it:
让我们看看我们是否可以通过将这个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.*");
}
We’ve used the AssertJ library to verify that the expected exception is thrown when we call bookList.get(0).getBookId() and that its message matches the one noted in our problem statement.
我们使用AssertJ库来验证当我们调用bookList.get(0).getBookId()时是否抛出了预期的异常,并且其消息与我们问题陈述中指出的一致。
The test passes, meaning that we’ve successfully reproduced the problem.
测试通过,意味着我们已经成功重现了这个问题。
2.3. Why the Exception Is Thrown
2.3.为什么抛出异常
If we take a closer look at the exception message class java.util.LinkedHashMap cannot be cast to class … Book, a couple of questions may come up.
如果我们仔细看一下异常信息class java.util.LinkedHashMap不能被投到class … Book,可能会有几个问题出现。
We’ve declared the variable bookList with the type List<Book>, but why does Jackson try to cast the LinkedHashMap type to our Book class? Furthermore, where does the LinkedHashMap come from?
我们已经用List<Book>类型声明了变量bookList,但是为什么Jackson试图将LinkedHashMap类型转换为我们的Book类?此外,LinkedHashMap是从哪里来的?
First, we indeed declared bookList with the type List<Book>. However, when we called the objectMapper.readValue() method, we passed ArrayList.class as the Class object. Therefore, Jackson will deserialize the JSON content to an ArrayList object, but it has no idea what type of elements should be in the ArrayList object.
首先,我们确实声明了bookList,类型为List<Book>。然而,当我们调用objectMapper.readValue()方法时,我们将ArrayList.class作为Class对象传递出去。因此,Jackson将把JSON内容反序列化为一个ArrayList对象,但是它不知道ArrayList对象中应该有什么类型的元素。
Second, when Jackson attempts to deserialize an object in JSON but no target type information is given, it’ll use the default type: LinkedHashMap. In other words, after the deserialization, we’ll get an ArrayList<LinkedHashMap> object. In the Map, the keys are the names of the properties, for example, “bookId”, “title” and so on.
其次,当Jackson试图反序列化一个JSON中的对象,但没有给出目标类型信息时,它将使用默认类型。LinkedHashMap。换句话说,在反序列化之后,我们会得到一个ArrayList<LinkedHashMap>对象。在Map中,键是属性的名称,例如,“bookId”,“title”等等。
The values are the values of the corresponding properties:
这些值是相应属性的值。
Now that we understand the cause of the problem, let’s discuss how to solve it.
现在我们了解了问题的原因,让我们来讨论如何解决这个问题。
3. Passing TypeReference to objectMapper.readValue()
3.将TypeReference传递给objectMapper.readValue()
To solve the problem, we need to somehow let Jackson know the type of the element. However, the compiler doesn’t allow us to do something like objectMapper.readValue(jsonString, ArrayList<Book>.class).
为了解决这个问题,我们需要以某种方式让Jackson知道这个元素的类型。然而,编译器不允许我们做类似objectMapper.readValue(jsonString, ArrayList<Book>.class)的事情。
Instead, we can pass a TypeReference object to the objectMapper.readValue(String content, TypeReference valueTypeRef) method.
相反,我们可以将一个TypeReference对象传递给objectMapper.readValue(String content, TypeReference valueTypeRef)/a>方法。
In this case, we just need to pass new TypeReference<List<Book>>() {} as the second parameter:
在这种情况下,我们只需要传递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);
}
If we run the test, it’ll pass. So, passing a TypeReference object solves our problem.
如果我们运行测试,它就会通过。所以,传递一个TypeReference对象可以解决我们的问题。
4. Passing JavaType to objectMapper.readValue()
4.将JavaType传递给objectMapper.readValue()
In the previous section, we talked about passing a Class object or a TypeReference object as the second parameter to call the objectMapper.readValue() method.
在上一节中,我们谈到传递一个Class对象或一个TypeReference对象作为第二个参数来调用objectMapper.readValue()方法。
The objectMapper.readValue() method still accepts a JavaType object as the second parameter. The JavaType is the base class of type-token classes. It’ll be used by the deserializer so that the deserializer knows what the target type is during the deserialization.
objectMapper.readValue()方法仍然接受一个JavaType对象作为第二个参数。JavaType是类型令牌类的基类。它将被反序列化器使用,以便反序列化器在反序列化过程中知道目标类型是什么。
We can construct a JavaType object through a TypeFactory instance, and we can retrieve the TypeFactory object from objectMapper.getTypeFactory().
我们可以通过TypeFactory实例构造一个JavaType对象,我们可以从objectMapper.getTypeFactory()中检索到TypeFactory对象。
Let’s come back to our book example. In this example, the target type we want to have is ArrayList<Book>.
让我们再来看看我们的书的例子。在这个例子中,我们想拥有的目标类型是ArrayList<Book>。
Therefore, we can construct a CollectionType with this requirement:
因此,我们可以根据这个要求构造一个CollectionType。
objectMapper.getTypeFactory().constructCollectionType(ArrayList.class, Book.class);
Now let’s write a unit test to see if passing a JavaType to the readValue() method will solve our problem:
现在让我们写一个单元测试,看看向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);
}
The test passes if we run it. So, the problem can be solved in this way as well.
如果我们运行它,测试就会通过。所以,这个问题也可以通过这种方式解决。
5. Using the JsonNode Object and the objectMapper.convertValue() Method
5.使用JsonNode对象和objectMapper.convertValue()方法
We’ve seen the solution of passing a TypeReference or JavaType object to the objectMapper.readValue() method.
我们已经看到了将一个TypeReference或JavaType对象传递给objectMapper.readValue()方法的解决方案。
Alternatively, we can work with tree model nodes in Jackson and then convert the JsonNode object into the desired type by calling the objectMapper.convertValue() method.
另外,我们可以在Jackson中处理树模型节点,然后将JsonNode对象转换成所需的类型,方法是调用objectMapper.convertValue()方法。
Similarly, we can pass an object of TypeReference or JavaType to the objectMapper.convertValue() method.
同样地,我们可以将一个TypeReference或JavaType的对象传递给objectMapper.convertValue()方法。
Let’s see each approach in action.
让我们看看每种方法的运作情况。
First, let’s create a test method using a TypeReference object and the objectMapper.convertValue() method:
首先,让我们使用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);
}
Now let’s see what happens when we pass a JavaType object to the objectMapper.convertValue() method:
现在让我们看看当我们将一个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);
}
If we run the two tests, both of them will pass. Therefore, using the objectMapper.convertValue() method is an alternative way to solve the problem.
如果我们运行这两个测试,它们都会通过。因此,使用objectMapper.convertValue()方法是解决这个问题的另一种方法。
6. Creating a Generic Deserialization Method
6.创建一个通用的反序列化方法
So far, we’ve addressed how to solve the class casting problem when we deserialize JSON array to Java collections. In the real world, we may want to create a generic method to handle different element types.
到目前为止,我们已经解决了当我们将JSON数组反序列化为Java集合时如何解决类的铸造问题。在现实世界中,我们可能想创建一个通用方法来处理不同的元素类型。
It won’t be a hard job for us now.
现在对我们来说,这不会是一件难事。
We can pass a JavaType object when calling the objectMapper.readValue() method:
在调用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);
}
Next, let’s create a unit test method to verify if it works as we expect:
接下来,让我们创建一个单元测试方法来验证它是否如我们所期望的那样工作。
@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);
}
The test will pass if we run it.
如果我们运行该测试,就会通过。
Why not use the TypeReference approach to build the generic method since it looks more compact?
为什么不使用TypeReference方法来构建通用方法,因为它看起来更紧凑?
Let’s create a generic utility method and pass the corresponding TypeReference object to the objectMapper.readValue() method:
让我们创建一个通用的实用方法,并将相应的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>>() {});
}
The method looks straightforward.
该方法看起来很简单。
If we run the test method once again, here’s what we’ll get:
如果我们再次运行这个测试方法,我们会得到以下结果。
java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class com.baeldung...Book ...
Oops, an exception occurred!
哎呀,出现了一个异常
We’ve passed a TypeReference object to the readValue() method, and we’ve previously seen that this way will solve the class casting problem. So, why are we seeing the same exception in this case?
我们将一个TypeReference对象传递给readValue()方法,我们之前已经看到,这种方式将解决类的铸造问题。那么,在这种情况下,我们为什么会看到同样的异常呢?
It’s because our method is generic. The type parameter T cannot be reified at runtime, even if we pass a TypeReference instance with the type parameter T.
这是因为我们的方法是通用的。类型参数T不能在运行时被重化,即使我们将一个TypeReference实例与类型参数T一起传递。
7. XML Deserialization With Jackson
7.用Jackson进行XML反序列化
Apart from JSON serialization/deserialization, the Jackson library can be used to serialize/deserialize XML as well.
除了JSON序列化/反序列化之外,Jackson库还可用于serialize/deserialize XML以及。
Let’s make a quick example to check if the same problem can happen when deserializing XML to Java collections.
让我们做一个快速的例子来检查在将XML反序列化为Java集合时是否会发生同样的问题。
First, let’s create an XML file books.xml:
首先,让我们创建一个XML文件books.xml。
<ArrayList>
<item>
<bookId>1</bookId>
<title>A Song of Ice and Fire</title>
<author>George R. R. 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>
Next, like we’ve done with the JSON file, we create another unit test method to verify if the class casting exception will be thrown:
接下来,就像我们对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.*");
}
Our test will pass if we give it a run. That is to say, the same problem occurs in XML deserialization as well.
如果我们让它运行一下,我们的测试就会通过。也就是说,在XML反序列化中也会出现同样的问题。
However, if we know how to solve JSON deserialization, it’s pretty simple to fix it in XML deserialization.
然而,如果我们知道如何解决JSON反序列化的问题,那么在XML反序列化中解决它就非常简单了。
Since XmlMapper is a subclass of ObjectMapper, all the solutions we’ve addressed for JSON deserialization also work for XML deserialization.
由于XmlMapper是ObjectMapper的子类,我们为JSON反序列化解决的所有解决方案也适用于XML反序列化。。
For example, we can pass a TypeReference object to the xmlMapper.readValue() method to solve the problem:
例如,我们可以将一个TypeReference对象传递给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. Conclusion
8.结语
In this article, we’ve discussed why we may get java.util.LinkedHashMap cannot be cast to X exception when we use Jackson to deserialize JSON or XML.
在这篇文章中,我们讨论了当我们使用Jackson对JSON或XML进行反序列化时,为什么会出现java.util.LinkedHashMap不能被转换为X异常。
After that, we’ve addressed different ways to solve the problem through examples.
之后,我们通过实例解决了解决问题的不同方法。
As always, the code in this write-up is all available over on GitHub.
一如既往,本篇文章中的代码都可以在GitHub上找到。