JsonJack:无法将java.util.LinkedHashMap强制转换为X
1.概述
Jackson是一个广泛使用的Java库,它使可以方便地对JSON或XML进行序列化/反序列化。
有时,当尝试将JSON或XML反序列化为对象集合时,可能会遇到“ java.lang.ClassCastException:java.util.LinkedHashMap无法转换为X”。
2.了解问题
让创建一个简单的Java应用程序来重现此异常,以了解何时会发生该异常。
2.1创建一个POJO类
public class Book {
private Integer bookId;
private String title;
private String author;
//getters, setters, constructors, equals and hashcode omitted
}
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"
} ]
ObjectMapper objectMapper = new ObjectMapper();
List<Book> bookList = objectMapper.readValue(jsonString, ArrayList.class);
System.out.println("test1:"+bookList);
System.out.println( bookList.get(0).getBookId());//这步出现问题
现在,如果仔细查看异常消息:“无法将类java.util.LinkedHashMap强制转换为类… Book”,可能会出现两个问题。
已经声明了类型为List 的bookList变量,但是为什么Jackson尝试将LinkedHashMap类型转换为Book类呢?此外,LinkedHashMap来自何处?
首先,实际上声明了类型为List 的bookList。但是,当我们调用objectMapper.readValue()方法时,我们将ArrayList.class传递为Class对象。因此,Jackson会将JSON内容反序列化为ArrayList对象,但不知道ArrayList对象中应该包含哪种类型的元素。
其次,当Jackson尝试在JSON中反序列化对象时,但未提供目标类型信息时,它将使用默认类型:LinkedHashMap。换句话说,反序列化之后,我们将获得一个ArrayList 对象。在地图中,键是属性的名称,例如“ bookId”,“ title”等。这些值是相应属性的值:
3.将TypeReference传递给objectMapper.readValue()
要解决该问题,需要以某种方式让Jackson知道元素的类型。 但是,编译器不允许我们执行诸如objectMapper.readValue(jsonString,ArrayList .class)之类的操作。
相反,可以将TypeReference对象传递给objectMapper.readValue(String content,TypeReference valueTypeRef)方法。 在这种情况下,我们只需要传递新的TypeReference <List >(){}作为第二个参数:
ObjectMapper objectMapper = new ObjectMapper();
List<Book> bookList = objectMapper.readValue(jsonString, new TypeReference<List<Book>>() {
});
System.out.println("test2:"+bookList);
System.out.println( bookList.get(0).getBookId());
4.将JavaType传递给objectMapper.readValue()
在上一节中,传递Class对象或TypeReference对象作为第二个参数来调用objectMapper.readValue()方法。
objectMapper.readValue()方法仍然接受JavaType对象作为第二个参数。 JavaType是类型标记类的基类。 反序列化器将使用它,以便反序列化器知道反序列化过程中的目标类型。
可以通过TypeFactory实例构造一个JavaType对象,并且可以从objectMapper.getTypeFactory()中检索TypeFactory对象。
在此示例中,要具有的目标类型是ArrayList 。 因此,我们可以按照以下要求构造一个CollectionType:
objectMapper.getTypeFactory().constructCollectionType(ArrayList.class, Book.class);
ObjectMapper objectMapper = new ObjectMapper();
CollectionType listType = objectMapper.getTypeFactory().constructCollectionType(ArrayList.class, Book.class);
List<Book> bookList = objectMapper.readValue(jsonString, listType);
System.out.println("test4:"+bookList);
System.out.println( bookList.get(0).getBookId());
5.使用JsonNode对象和objectMapper.convertValue()方法
上面已经看到了将TypeReference或JavaType对象传递给objectMapper.readValue()方法的解决方案。
另外,可以在Jackson中使用树模型节点,然后通过调用objectMapper.convertValue()方法将JsonNode对象转换为所需的类型。
同样,可以将TypeReference或JavaType对象传递给objectMapper.convertValue()方法。
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(jsonString);
List<Book> bookList = objectMapper.convertValue(jsonNode,
objectMapper.getTypeFactory().constructCollectionType(ArrayList.class, Book.class));
System.out.println("test2:"+bookList);
System.out.println( bookList.get(0).getBookId());
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);
}
为什么不使用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: java.util.LinkedHashMap cannot be cast to com.example.demo.web.Testb$Book
将TypeReference对象传递给readValue()方法,并且以前已经看到这种方法可以解决类转换问题。 那么,为什么在这种情况下会看到相同的异常?
这方法是通用的。 即使使用类型参数T传递TypeReference实例,也无法在运行时对类型参数T进行验证。