1、描述

最近在使用 Jackson 将 Json 串转换回 Java 对象的时候遇到了 ClassCastException 错误,特此记述。

2、问题复现

问题出现的节点在于属性节点的 JavaType 不明确,比如使用了泛型 和 Object,如下:

 1     @Getter
 2     @Setter
 3     @NoArgsConstructor
 4     @AllArgsConstructor
 5     private static class JsonResult<T> {
 6         private String message;
 7         private T result; // 问题出现在这里
 8     }
 9 
10     @Builder
11     @Getter
12     @Setter
13     @NoArgsConstructor
14     @AllArgsConstructor
15     private static class Container {
16         private String key;
17         private String value;
18     }

使用如下的测试的用例

 1     @Test
 2     void testWrite() throws JsonProcessingException {
 3         final ObjectMapper mapper = new ObjectMapper();
 4         mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
 5         final JsonResult<Container> resultBefore = new JsonResult<>();
 6         resultBefore.setMessage("foo");
 7         final Container containerBefore = Container.builder().key("key").value("value").build();
 8         resultBefore.setResult(containerBefore);
 9         final String writtenString = mapper.writeValueAsString(resultBefore);
10         //----------------read str as object
11         final JsonResult parsedResult = mapper.readValue(writtenString, JsonResult.class);
12         assert parsedResult != null; // 之后将在这里打断点
13 
14         final Container container = (Container) parsedResult.result;
15         assertThat(container).as("not null").isNotNull().extracting(Container::getKey).as("key equal").isEqualTo(containerBefore.key);
16         assertThat(container).extracting(Container::getValue).as("key equal").isEqualTo(containerBefore.value);
17     }
18 }

将会得到错误: java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to Container

3、问题解析

让我们在测试中打上断点,看看经由 jackson 反序列化后的对象内容

 

  可以看到这时候 Container 类的内容在这里变成了 hashMap,其实各种原因不难理解,在序列化跟反序列化中,我们给 Jackson 传递的 Class 是 JsonResult,回到相应类的定义,会发现我们使用的是泛型,Jackson 并不知道 rsult 中实际存放的类型,对 Object 也是如此(Object 是所有非原始类型的祖先)

  在 ide 的提示里,我们也可以看到这时候 parsedResult 里边的 result 实际上是 Object 类型

4、思路与解决方式

4.1 思路

  从上边的分析来,看解决的思路很简单,我们需要告诉 Jackson  result 中存放的数据类型。方法有二

4.2 解决方式

4.2.1 改变反序列化时传递的 Class 参数

4.2.1.1 泛型

  当对象有泛型参数时候,我们只要构建一个新类型,让它继承原本的类并指定泛型参数即可。在原本的代码中,我们加入。 

1 private static class ContainerJsonRsult extends JsonResult<Container>{}

  并且改变相应的反序列化语句即可。

final ContainerJsonRsult parsedResult = mapper.readValue(writtenString, ContainerJsonRsult.class);

4.2.1.2 Object

  当对象的没有泛型签名时,我们需要构建一个新类型,让他继承原本的类,并让他拥有目标 Containner 类型的同名参数 result

1     @Getter
2     @Setter
3     @NoArgsConstructor
4     @AllArgsConstructor
5     private static class ContainerJsonResult extends JsonResult {
6         private Container result;
7     }

  然后修改对应的反序列语句:

1 final ContainerJsonResult parsedResult = mapper.readValue(writtenString, ContainerJsonResult.class);

2、在反序列化中手动传递 result 对应的 Calss 类型

对于存在泛型的类,推荐 4.2.1.1 的解决方式,当 rsult 中指向的是 Object 或者 T  类型时,都可以指定相应的 Class 类进行二次转换:

1  Container container = mapper.convertValue(parsedResult.getResult(), Container.class);

  或者

1         JavaType type = mapper.getTypeFactory().constructType(Container.class);
2         Container container = mapper.convertValue(parsedResult.result, type);

 

 

 

 

 

 

posted on 2019-12-10 17:11  四维胖次  阅读(606)  评论(0编辑  收藏  举报