含有泛型的 JSON 反序列化问题
含有泛型的 JSON 反序列化
- 一、背景
- 二、分析
- 2.1 事出诡异必有妖
- 三、解决之道
- 3.1 猜想验证
- 3.2 举一反三
- 四、总结
一、背景
今天无聊之园提了一个问题,涉及的示例大致如下:
public static void main(String[] args) {String jsonString = "[\"a\",\"b\"]";List<String> list = JSONObject.parseObject(jsonString, List.class);System.out.println(list);
}
例子中使用fastjson 的类库。
为什么 IDEA 会给出下面的警告,该如何解决?
有些同学说直接使用抑制注解,抑制掉这个警告就好了。
抑制掉警告就可以了????
二、分析
2.1 事出诡异必有妖
IDEA 不会无缘无故给出警告提示,警告的原因上图已经给出。
把不带泛型的 List 赋值给带泛型的 List, Java 编译器并不知道右侧返回不带泛型的实际 List 是否符合带泛型的 List 约束。
和下面的例子非常类似:
public static void main(String[] args) {List first = new ArrayList();first.add(1);first.add("2");first.add('3');// 提示上述警告List<String> third = first;System.out.println(third);}
将 first 赋值给 third 时,不能保证 first 元素符合 List的约束,即列表中全是 String。
如果你执行上述代码,会发现没有报错,哈哈。
但是如果你使用 foreach 循环或者迭代器取 String 循环时会发生类型转换异常。
public static void main(String[] args) {List first = new ArrayList();first.add(1);first.add("2");first.add('3');List<String> third = first;for (String each : third) { // 类型转换异常System.out.println(each);}}
类型转换异常?
我们使用 IDEA 的 jclasslib 反编译插件,得到 main 函数的 Code 如下:
0 new #2 <java/util/ArrayList>3 dup4 invokespecial #3 <java/util/ArrayList.<init>>7 astore_18 aload_19 iconst_1
10 invokestatic #4 <java/lang/Integer.valueOf>
13 invokeinterface #5 <java/util/List.add> count 2
18 pop
19 aload_1
20 ldc #6 <2>
22 invokeinterface #5 <java/util/List.add> count 2
27 pop
28 aload_1
29 bipush 51
31 invokestatic #7 <java/lang/Character.valueOf>
34 invokeinterface #5 <java/util/List.add> count 2
39 pop
40 aload_1
41 astore_2
42 aload_2
43 invokeinterface #8 <java/util/List.iterator> count 1
48 astore_3
49 aload_3
50 invokeinterface #9 <java/util/Iterator.hasNext> count 1
55 ifeq 79 (+24)
58 aload_3
59 invokeinterface #10 <java/util/Iterator.next> count 1
64 checkcast #11 <java/lang/String>
67 astore_4
69 getstatic #12 <java/lang/System.out>
72 aload_4
73 invokevirtual #13 <java/io/PrintStream.println>
76 goto 49 (-27)
79 return
从 42 到76 行 对应 foreach 循环的逻辑,可以看出底层使用 List 的迭代器进行遍历,取出每个元素后强转为 String 类型,存储到局部变量表索引为 4 的位置,然后进行打印。
如果对反编译不熟悉可以去 target 目录,双击编译后的class 文件,使用 IDEA 自带的插件进行反编译:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//package com.chujianyun.common.json;import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;public class JsonGenericDemo {public JsonGenericDemo() {}public static void main(String[] args) {List first = new ArrayList();first.add(1);first.add("2");first.add('3');List<String> third = first;Iterator var3 = first.iterator();while(var3.hasNext()) {String each = (String)var3.next();System.out.println(each);}}
}
印证了上述说法,显然在 String each = (String)var3.next();
这里出现了类型转换异常。
三、解决之道
3.1 猜想验证
我们猜测是不是可以通过某种途径将泛型作为参数传给 fastjson, 让 fastjson 某个返回值是带泛型的,从而解决这个告警呢?
显然我们要去源码中寻找, 在 JSONObject 类中找到了下面的方法:
/*** <pre>* String jsonStr = "[{\"id\":1001,\"name\":\"Jobs\"}]";* List<Model> models = JSON.parseObject(jsonStr, new TypeReference<List<Model>>() {});* </pre>* @param text json string* @param type type refernce* @param features* @return*/@SuppressWarnings("unchecked")public static <T> T parseObject(String text, TypeReference<T> type, Feature... features) {return (T) parseObject(text, type.type, ParserConfig.global, DEFAULT_PARSER_FEATURE, features);}
该函数的注释上还贴心地给出了相关用法,因此我们改造下:
public static void main(String[] args) {String jsonString = "[\"a\",\"b\"]";List<String> list = JSONObject.parseObject(jsonString, new TypeReference<List<String>>() {});System.out.println(list);
}
警告解除了。
所以大功告成?
难道上述做法仅仅是为了消除一个警告,满足强迫症们的心愿而已吗??
且慢,我们看下面的例子:
import lombok.Data;@Data
public class User {private Long id;private String name;
}
mport com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;import java.util.ArrayList;
import java.util.List;public class JsonGenericDemo {public static void main(String[] args) {// 构造数据User user = new User();user.setId(0L);user.setName("tom");List<User> users = new ArrayList<>();users.add(user);// 转为JSON字符串String jsonString = JSON.toJSONString(users);// 反序列化List<User> usersGet = JSONObject.parseObject(jsonString, List.class);for (User each : usersGet) {System.out.println(each);}}}
大家执行上述例子会出现类型转换异常!
Exception in thread “main” java.lang.ClassCastException: com.alibaba.fastjson.JSONObject cannot be cast to com.chujianyun.common.json.User
at com.chujianyun.common.json.JsonGenericDemo.main(JsonGenericDemo.java:26)
有了第二部分的分析,大家可能就可以比较容易地想到
JSONObject.parseObject(jsonString, List.class) 构造出来的 List 存放的是 JSONObject 元素, foreach 循环底层使用迭代器遍历每个元素并强转为 User 类型是报类型转换异常。
那么为啥 fastjson 不能帮我们转换为 List<User>
类型呢?
有人说“由于泛型擦除,没有泛型信息,所以无法逆向构造回原有类型”。
其实看下 JSONObject.parseObject(jsonString, List.class);
第一个参数是字符串,第二个参数是 List.class。压根就没有提供泛型信息给 fastjson。
作为这个工具函数本身,怎么猜得到要 List 里面究竟该存放啥类型呢?
因此如果能够通过某种途径,告诉它泛型的类型,就可以帮助你反序列化成真正的类型。
使用 JSONObject.parseObject(jsonString, new TypeReference<List<User>>() { });
即可。
因此我们使用 TypeReference 并不仅仅是为了消除警告,而是为了告知 fastjson 泛型的具体类型,正确反序列化泛型的类型。
那么底层原理是啥呢?我们看下
com.alibaba.fastjson.TypeReference#TypeReference()
/*** Constructs a new type literal. Derives represented class from type* parameter.** <p>Clients create an empty anonymous subclass. Doing so embeds the type* parameter in the anonymous class's type hierarchy so we can reconstitute it* at runtime despite erasure.*/protected TypeReference(){// 获取父类的 TypeType superClass = getClass().getGenericSuperclass();// 如果父类是参数化类型,会返回 java.lang.reflect.ParameterizedType// 调用 getActualTypeArguments 获取实际类型的数组 并拿到第一个Type type = ((ParameterizedType) superClass).getActualTypeArguments()[0];// 缓存中有优先取缓存,没有则存入并设置Type cachedType = classTypeCache.get(type);if (cachedType == null) {classTypeCache.putIfAbsent(type, type);cachedType = classTypeCache.get(type);}this.type = cachedType;}
通过代码和注释我们了解到:
创建一个空的匿名子类。将类型参数嵌入到匿名继承结构中,即使运行时类型擦除也可以重建。
再回到 parseObject 函数,可以看到底层用的就是这个 type。
/*** <pre>* String jsonStr = "[{\"id\":1001,\"name\":\"Jobs\"}]";* List<Model> models = JSON.parseObject(jsonStr, new TypeReference<List<Model>>() {});* </pre>* @param text json string* @param type type refernce* @param features* @return*/@SuppressWarnings("unchecked")public static <T> T parseObject(String text, TypeReference<T> type, Feature... features) {return (T) parseObject(text, type.type, ParserConfig.global, DEFAULT_PARSER_FEATURE, features);}
3.2 举一反三
很多其他框架也会采用类似的方法来获取泛型类型。
大家可以看看其他 gson 类库
<dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.8.6</version>
</dependency>
看看其中的 com.google.gson.reflect.TypeToken
类,是不是似曾相识呢?
此外,如果我们自己除了 JSON反序列化场景之外也有类似获取泛型参数的需求,是不是也可以采用类似的方法呢?
四、总结
希望大家能够重视 IDEA 的警告。
遇到问题能够从更合理的角度思考,了解问题的本质。
学习一个问题可以尝试举一反三,活学活用。
希望本文对大家有帮助,创作不易,如果对你有帮助,欢迎关注,点赞。
您的支持和鼓励是我创作的最大动力。
含有泛型的 JSON 反序列化问题相关推荐
- Java中含有泛型的 JSON 反序列化问题
作者:明明如月学长 blog.csdn.net/w605283073/article/details/107350113 一.背景 今天无聊之园提了一个问题,涉及的示例大致如下: public st ...
- Json反序列化与Java泛型
Java的JSON库有很多,本文分析google的Gson和alibaba的fastjson,在Java泛型场景反序列化的一些有意思的行为.考虑下面的json字符串: ["214748364 ...
- android 生成泛型对象,java android解析多层含有泛型对象的json数据获取不到泛型类型解析失败解决办法...
####问题描述 * java 解析多层含有泛型对象的json数据获取不到泛型类型 * 如果将泛型改成实际的类型就能正常解析 * 如果不改成实际的类型泛型数据被解析成com.google.gson.i ...
- 定义和使用含有泛型的接口
含有泛型的接口 定义格式: 修饰符 interface接口名<代表泛型的变量> { } 例如, public interface MyGenericInterface<E>{p ...
- 定义和使用含有泛型的方法
含有泛型的方法 定义格式: 修饰符 <代表泛型的变量> 返回值类型 方法名(参数){ } 例如, public class MyGenericMethod { public <MVP ...
- 定义和使用含有泛型的类
泛型的定义与使用 我们在集合中会大量使用到泛型,这里来完整地学习泛型知识. 泛型,用来灵活地将数据类型应用到不同的类.方法.接口当中.将数据类型作为参数进行传递. 定义和使用含有泛型的类 定义格式: ...
- Newtonsoft.Json反序列化(Deserialize)出错:Bad JSON escape sequence
使用Newtonsoft.Json反序列化收到的字串为JObject或其它支持的数据模型,有时错误,提示如下: Bad JSON escape sequence: \c. Path 'idno', l ...
- qt中生成含有中文的json文件和解析json文件
提要 生成的json文件在程序加载解析时出现上述错误,究其原因是生成json文件过程中编码问题.qt编译器默认的编码格式为utf-8,而windows一般为gbk编码,所以就需要在生成本地json文件 ...
- qt中生成含有中文的json文件,读取含有中文的json文件
引言 之前将变量保存并在本地生成json文件,由于其中含有中文,导致生成的json文件出现乱码,或者就是生成的json文件没有乱码,但是读取生成的json文件时出现乱码,不能正常解析json. 示例 ...
最新文章
- luogu P3203 [HNOI2010]弹飞绵羊(LCT ? 暴力分块 ! )
- GARFIELD@09-28-2004
- EOS (3)系统特点
- 贵港天气预报软件测试,贵港天气预报15天
- Python+Selenium基础篇之2-打开和关闭火狐浏览器
- python arima模型_Python时间序列处理之ARIMA模型的使用讲解
- 一边学,一边写出的人工智能教程(一)
- 配置不成功_在windows下配置sendmail服务器
- 公务员“上班睡觉”为何会被强势围观?
- cmd模式下用cd命令进入其他盘或者文件夹
- 五笔中比较难拆解的字
- elasticsearch学习笔记--聚合函数篇
- 浅析swift optional
- 车祸相关公开数据集(免费下载)
- S7-1200变量地址
- kpw4换壁纸_kindle 篇五:kindle paperwhite4使用30天总结
- 混沌工程——各大厂实践分享汇总(下篇)
- 尬聊器(伪聊天机器人)
- 高通骁龙845的android手机有哪些,骁龙845手机有哪些 2018搭载高通骁龙845的手机推荐...
- 2.12 IC类元器件的封装应该怎么创建?