深圳幻海软件技术有限公司 欢迎您!

当JSON解析遇上了泛型,该如何处理泛型擦除问题

2023-02-27

JSON是一种轻量级的数据交换格式,简洁和清晰的层次结构使得JSON成为理想的数据交换语言,常被用于实际项目中。Java生态圈中有很多处理JSON格式化的类库,如json-lib框架、Jackson、Google的Gson、阿里的FastJson等,通过这些类库可以使我们更加简便地处理JSON。下面

JSON是一种轻量级的数据交换格式,简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言,常被用于实际项目中。Java生态圈中有很多处理JSON格式化的类库,如json-lib框架、Jackson、Google的Gson、阿里的FastJson等,通过这些类库可以使我们更加简便地处理JSON。下面本文主要通过一个实际案例讨论分析在解析JSON字符串的过程中遇到泛型该如何处理。

  • 案例描述
  • 问题引出
  • 处理方案
  • 原理分析

一、案例描述

首先介绍三个类,为了便于理解将代码省去非关键部分。

  • PrepCommonResp 是一个通用响应类,为了支持多种返回类型,设置了其中一个属性result为泛型。
  • public class PrepCommonResp<T> { 
     private T result; //获取调用返回值 
     private String code = "000000"; //获取错误码 
     private String msg = "Success"
    //这里省略getter setter等 
    
    
    • 1.
    • 2.
    • 3.
    • 4.
    • 5.
    • 6.
  • LeaderboardResp 是一个Leaderboard接口响应类,其中有一个属性是一个List集合集合中的对象为LeaderboardPojo
  • public class LeaderboardResp implements Serializable { 
     List<LeaderboardPojo> leaderboardList; 
     //这里省略getter setter等 
    
    
    • 1.
    • 2.
    • 3.
    • 4.
  • LeaderboardPojo 是一个实体类
  • public class LeaderboardPojo { 
    //基本属性 
    
    
    • 1.
    • 2.
    • 3.

需求:将一个JSON字符串的String类型数据解析到指定了泛型的通用响应类实例PrepCommonResp对象中。

二、问题引出

一开始编写时,采用了Jackson的ObjectMapper类readValue方法来进行解析,具体代码如下。

ObjectMapper mapper = new ObjectMapper(); 
PrepCommonResp<LeaderboardResp> resp = mapper.readValue(result,PrepCommonResp.class); 
  • 1.
  • 2.

我们期待的结果是JSON字符串被正确解析,并且result属性的值是以指定泛型LeaderboardResp的格式存储。但是通过断点之后我们发现result属性的值是以LinkedHashMap的形式存储,与期望结果不符。

三、解决方案

在分析原因之前,先来说说解决方式。解决方式有多种,我们通过了尝试对比了各种方案之后选择了其中一种较为简便的就是采用Gson的fromJson方法来解决,具体代码如下:

//尝试使用Gson 
Gson gson = new Gson(); 
PrepCommonResp<LeaderboardResp> resp = gson.fromJson(result, new TypeToken<PrepCommonResp<LeaderboardResp>>(){}.getType()); 
List<LeaderboardPojo> list = resp.getResult().getLeaderboardList(); 
  • 1.
  • 2.
  • 3.
  • 4.

运行结果如下,result的值以我们期望的LeaderboardResp形式存储

四、原理分析

1. 先理解泛型与编译器虚拟机的关系以及什么是擦除?

解析: Java语言的泛型基本上是在编译器中实现的。由编译器执行类型检测和推断后生成普通的非泛型的字节,虚拟机是完全无感知泛型存在的,这种实现技术称为擦除。编译器使用泛型类型信息保证类型安全,然后在生成字节码之前将其清除。

2. 为什么使用Jackson的readValue方法解析JSON字符串后result的属性值会是LinkedHashMap而不是我们指定的LeaderboardResp呢?

解析:泛型只在编译期间起到检测作用,当编译器将泛型类编译完成之后,泛型类的类型参数都被全部擦除。接下来在运行期间虚拟机并不知道泛型的存在,当对JSON字符串进行解析时由于泛型被擦除了导致虚拟机并不知道要将其解析成哪种类型,所以就解析为了默认的LinkedHashMap类型,导致出现了上面的场景。

3. 那Gson是如何解决泛型擦除这种情况呢?

我们来看看这一句核心代码

PrepCommonResp<LeaderboardResp> resp = gson.fromJson(result, new TypeToken<PrepCommonResp<LeaderboardResp>>(){}.getType()); 
  • 1.

解析:Gson的做法非常巧妙,如上面的代码所示,将需要获取类型的泛型类作为TypeToken的泛型参数构造一个匿名的子类,然后通过getType()方法就可以获取到我们想要的泛型类的泛型参数类型。可以理解为是将泛型类型存起来,解决了泛型擦除的问题。

五、最后

感谢您的阅读,如果喜欢本文欢迎关注和转发,本头条号将持续分享IT技术知识。对于文章内容有其他想法或意见建议等,欢迎提出共同讨论共同进步。如果您对于此场景有更好的解决方案也欢迎提出讨论。