版本:fastjson 1.2.76
环境:springboot 2.3.6
问题概述:通过构造 TypeReference 实现泛型反序列化,但实际上序列化出来的并不是想要的实际类型。并且,只有在于线上的某个节点出现,且本地无法复现。
简化业务逻辑代码:
BaseResponse<T>
装载通用的接口响应,其属性 private T data
是不同类型的数据;CountryResponse
通过 json 数据反序列出:BaseResponse<CountryResponse>
HttpRequestUtil
,有个 static 方法将接口的 response body 反序列出 BaseResponse<T>
@Setter
@Getter
@ToString
public static class BaseResponse<T> {
private Integer code;
private String msg;
private T data;
}
@Setter
@Getter
@ToString
public static class CountryResponse {
private String countryId;
private String countryName;
}
public static class HttpRequestUtil {
public static <T> List<T> doGet4List(Class<T> responseCls) {
// 工具类方法,body 变量模拟了请求响应 {"code":0,"success":true,"msg":"success","data":[{"countryId":"CN","countryName":"China"}]}
String body = "{\"code\":0,\"success\":true,\"msg\":\"success\",\"data\":[{\"countryId\":\"CN\",\"countryName\":\"China\"}]}";
log.info("doGet4List result: [{}]", body);
BaseResponse<List<T>> baseResponse = JSON.parseObject(body, new TypeReference<>(responseCls) {
});
return Optional.ofNullable(baseResponse).map(BaseResponse::getData).orElse(null);
}
}
问题报错代码
@Test
public void testFastJsonParse() {
List<CountryResponse> countryResponses = HttpRequestUtil.doGet4List(CountryResponse.class);
for (CountryResponse countryResponse : countryResponses) { // 这行报错了
System.out.println(countryResponse.getCountryId());
}
}
查看线上日志,报错行数是:for (CountryResponse countryResponse : countryResponses) {
这一行。
java.lang.ClassCastException: class com.alibaba.fastjson.JSONObject cannot be cast to class com.foo.entity.CountryResponse(com.alibaba.fastjson.JSONObject and com.foo.entity.CountryResponse are in unnamed module of loader org.springframework.boot.loader.LaunchedURLClassLoader @2a2d45ba)
猜测:countryResponses 这个 List 的元素实际上是 JSONObject 对象,遍历时候强转了一次。
主要是线上的所有节点都是同样的镜像,只有某个节点会出这个错误,本地无法复现,拿相同的 json 数据去反序列,都能正确反序列出 CountryResponse 。这个工具类方法都跑了大半年了。。。
自我怀疑人生 ing......
描述有误,应改为:
简化业务逻辑代码:
1
sumarker 2023-08-18 15:11:11 +08:00
我没太懂 BaseResponse<List<T>> 这个里不是一个 list 吗? responseCls 不是 object.class 吗?
|
2
oneisall8955 OP @sumarker #1
接口定义:public static <T> List<T> doGet4List(Class<T> responseCls) {... } 接口调用:List<CountryResponse> countryResponses = HttpRequestUtil.doGet4List(CountryResponse.class); 这里入参不是传递了 CountryResponse.class 了吗? 另外,是不是上面例子用错了 api ,应该用 fastjson 的其他 api 来反序列化 BaseResponse<List<T>? |
3
sumarker 2023-08-18 15:37:49 +08:00
@oneisall8955 #2
我可能没说明白: 1. 我记得 TypeReference 的用法是 在 <> 里指明转换的类型 2. 你的 BaseResponse 里写的是 List<T> 但是你传入方法的是 CountryResponse.class ,我理解是 你的 T 是 CountryResponse ? 那你是要将 body 转成 BaseResponse<T> ? 所以我上面说没看懂你的意图 (另外 你的代码是 java 环境吧?) |
4
oneisall8955 OP @sumarker #3 Sorry ,我写的有问题
- 普通类 CountryResponse 通过 json 数据反序列出:BaseResponse<CountryResponse> 这里我写错了,应该为 BaseResponse<List<CountryResponse>> - 是 Java 环境 - TypeReference 确实可以在<>这里指明,例如 BaseResponse<List<T>> baseResponse = JSON.parseObject(body, new TypeReference<>(responseCls); 这里是个简化的写法,你应该也看明白。实际是 BaseResponse<List<T>> baseResponse = JSON.parseObject(body, new TypeReference<BaseResponse<List<T>>>(responseCls) { });,被编辑器提示且自动简化了。 - TypeReference<>这里指明,那么 如果想反序列 Foo.class ,那就写一个方法 new TypeReference<BaseResponse<List<Foo>>>(){}; 如果想反序列 Bar.class ,那就写一个方法 new TypeReference<BaseResponse<List<Bar>>>(){}; 确实这样也可以,但是会重复大量类似代码。想提供一个传入 Class<T> clazz 参数的方法,该方法返回对应的 List<T>,就如同 doGet4List 的用途。很可惜,并不能 new TypeReference<BaseResponse<List<clazz>>>(){};这样是不符合语法的,那么,该如何正确的编写这样的方法呢? - 让我疑惑的是,如果这种写法是错误的,为什么集群中只有一个实例会抛出异常,其他的实例都正常反序列化 |
5
yazinnnn 2023-08-18 16:17:04 +08:00
doGet4List 改成这样
public static <T> List<T> doGet4List(String body,TypeReference<BaseResponse<List<T>>> typeReference) { BaseResponse<List<T>> resp = JSON.parseObject(body, typeReference); return resp.data; } java 运行时无法获取到泛型的真实类型, 如果你用 kotlin 可以 inline + refied 实现你期望的效果 inline fun <reified T> doGet4List(body: String): List<T> { val resp = JSON.parseObject(body, object : TypeReference<BaseResponse<List<T>>>() {}) return resp.data } fun main() { val list = doGet4List<CountryResponse>("body") } |
6
4kingRAS 2023-08-18 16:20:51 +08:00
就别用这垃圾,jackson gson 不香吗
|
7
sumarker 2023-08-18 16:32:43 +08:00
@oneisall8955 #4
"被编辑器提示且自动简化了。" -- 我用 JetBrains (#IU-232.8660.185 )没有发现这样的提示 想反 如果你没写 ,编译器是会报错的 TypeReference<>这里指明 的问题 -- 因为你在方法外已经指明方法的泛型 T ,那么,你在实际运行过程中已经将 T 那么 你直接用 上面写的 BaseResponse<List<T>> baseResponse = JSON.parseObject(body, new TypeReference<BaseResponse<List<T>>>(responseCls) { }); 就可以了。 至于你说的问题,我尝试着复现 了一下(只能大概复现出来),实际出错的部分确实是 转换的部分(如果你提供的代码经过处理的话) 出现的原因可能是 数据中的 data 为空 或者 null |
8
oneisall8955 OP |
9
cheng6563 2023-08-18 18:00:39 +08:00
这个 new TypeReference<>(responseCls)确实有问题,首先这个泛型应该是会被擦掉的。
但是你传了个指定的类型 responseCls 进去,fastjson 应该会使用 responseCls 进行反序列化。 但我稍微瞄了一眼源代码,new TypeReference<Foo>(){} 和 new TypeReference<>(Foo.class){}的行为不是一样的。 你的 doGet4List 方法应当提供 TypeReference 参数而不是提供 Class 参数。 |
10
TanKuku 2023-08-19 01:40:58 +08:00 via Android
不传方法参数,用泛型参数相当于声明一个固定泛型的 class ,这个是运行时可以获取的,传了参数就相当于泛型擦除掉了,只是一个泛型方法
|
11
ikas 2023-08-19 10:22:47 +08:00
应该是这里 TypeReference 的实现方式要求直接使用具体类型..使用传递不行..
如果想传递,需要自己实现内部获取 Type 的方式..通过反射推断具体类型还是比较复杂的.. -- 如果是传递一个 class 类型的,使用 jackson,一般是使用其 JavaType 来构造类型,fastjson 从来不用,不知是否有这样的 api |