Gson反序列化包含泛型的对象

最近在重构一个遗留项目,项目中关于Json序列化/反序列化操作几乎都是显示的手动序列化,代码散落在各个方法中,丝毫没有利用框架的自动序列化机制。
重构的第一步是去除冗余和提炼共通方法。所以将序列化与反序列提取出来。
但提取反序列化带泛型的对象出了问题。
由于泛型在编译时会被擦除的特性,一般情况下要反序列化包含泛型的对象时需要用到Gson的TypeToken用于返回泛型类。举例:

Response
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* 响应对象。<br/>
* {@link #errMesg}和{@link #errDetail}仅在状态{@link #status}为{@link Status#ERROR}时出现。
*
* @author liuxinsi
* @mail akalxs@gmail.com
*/
@Data
public class Response<T> {
/**
* 业务数据
*/
private T data;
/**
* 响应状态
*/
private Status status;
/**
* 错误消息
*/
private String errMesg;
/**
* 错误堆栈
*/
private String errDetail;

public enum Status {
SUCCESS, ERROR;
}
}
ValidateTokenResponse
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 对应{@link com.lxs.mms.auth.resource.AuthenticationResource#validateToken(ValidateTokenRequest)} 响应。
*
* @author liuxinsi
* @mail akalxs@gmail.com
*/
@Data
@ApiModel(value = "验证Token响应")
public class ValidateTokenResponse {
@ApiModelProperty(value = "Token是否合法,true合法,false非法。")
private Boolean legally;
@ApiModelProperty(value = "Token中的Audience属性")
private String audience;
@ApiModelProperty(value = "描述")
private String desc;

}

包装类Response用于抽象共通的响应,其中data是各个业务的对象,所以是个泛型。
ValidateTokenResponse,具体的业务响应。封装时候将对象Set到Response后进行序列化。

反序列化时由于泛型被擦,如果直接用fromJson方法则data的类型不会是期望的业务类,而是List<LinkedHashMap>。
所以Gson提供了TypeToken用于处理这个问题:

1
2
3
4
Gson gson = new Gson();
Type jsonType = new TypeToken<Response<ValidateTokenResponse>>() {
}.getType();
Response<ValidateTokenResponse> r=gson.fromJson(json, jsonType);

由于TypeToken的构造是protected的,所以需要new一个匿名子类(anonymous subclass),在构造的时候TypeToken会根据你显示传入的泛型获取泛型类并作为ParameterizedType返回。这样在fromJson的时候就可以知道具体的泛型类,然后反序列化时就可以得到正确的data类型。

所以在提取这种代码时候进行的封装则按常理应该是:

1
2
3
4
5
6
public static <T> Response<T> unwrap(String json) {
Gson gson = new Gson();
Type jsonType = new TypeToken<Response<T>>() {
}.getType();
return gson.fromJson(json, jsonType);
}

看起来毫无破绽,编译运行都没有问题,但就是data属性还是List
因为上面的T是一个类型参数(java.lang.reflect.TypeVariable)并不是具体的业务类。所以TypeToken没法取得期待的泛型类。
根据TypeToken的源码来看它在获取泛型类的时候实际上返回了一个ParameterizedType用于代表泛型类。
所以按照思路只要实现这个接口然后告诉它正确的泛型类就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public static <T> Response<T> unwrap(String json, Class<?> clazz) {
return gson.fromJson(json, new ParameterizedType() {
/**
* 原始类型实际的泛型类,与外部传入<code>clazz</code>显示指明。
*/
@Override
public Type[] getActualTypeArguments() {
return new Class[]{clazz};
}

/**
* 原始类型。
*/
@Override
public Type getRawType() {
return Response.class;
}

/**
* 如果是内部类需要指明所属的对象,如果不是返回null。
*/
@Override
public Type getOwnerType() {
return null;
}
});

}

调用时:

1
Response<ValidateTokenResponse> r = ResponseUnwrap.unwrap(json,ValidateTokenResponse.class);

就可以正确的处理反序列化。
其实还可以封装的更灵活点,比如处理多个泛型需要修改传参和getActualTypeArguments方法的返回,如果是传集合类型还要单独处理下,不应该写死RawType。