本文主要尝试对类似报文结构的json做反序列化,报文比如:
public class Msg {
public static final int MSG_TYPE_SPEAK = 0;
public static final int MSG_TYPE_COUNT = 1;
@IntDef({MSG_TYPE_SPEAK, MSG_TYPE_COUNT})
@Retention(RetentionPolicy.SOURCE)
public @interface MsgType {}
private int type;
private AbsMsgBody body;
public Msg(int type, AbsMsgBody body) {
this.type = type;
this.body = body;
}
public @MsgType int getType(){
return type;
}
public AbsMsgBody getBody() {
return body;
}
}
其中msgType决定了后面body这个抽象类字段具体是那种实例。下面是body:
public abstract class AbsMsgBody {
}
public class MsgBodySpeak extends AbsMsgBody{
private String words;
public MsgBodySpeak(String words) {
this.words = words;
}
public String getWords() {
return words;
}
}
public class MsgBodyCount extends AbsMsgBody {
private long count;
public MsgBodyCount(int count) {
this.count = count;
}
public long getCount() {
return count;
}
}
之前有做过使gson解析更健壮的尝试,解决服务端下发数据与实际类型冲突导致解析失败的问题:
https://blog.csdn.net/starry_eve/article/details/100546877
今天直接尝试对抽象类进行解析,有点野。
从上一篇文章可以知道,gson反序列化过程中,解析对象中的对象字段,会递归调用TypeAdapter,每个字段的解析实际就是对字段类型所对应的TypeAdapter的read方法的调用。
所以我们的思路就是,拦截TypeAdapter的方法,如果能提前获取到msgType,稍后则可以知道要把AbsMsgBody这个TypeAdapter替换成哪个派生类的TypeAdapter。
今天使用的核心方法Gson#getDelegateAdapter:
public <T> TypeAdapter<T> getDelegateAdapter(TypeAdapterFactory skipPast, TypeToken<T> type) {
...
}
这个方法允许使用者根据type来创建默认的TypeAdapter。
第二个方法GsonBuilder#registerTypeAdapterFactory
public GsonBuilder registerTypeAdapterFactory(TypeAdapterFactory factory) {
...
}
这个方法允许使用者自定义TypeAdapter的创建。
下面配合使用两者,先创建一个Factory实例:
public class AbsTypeAdapterFactory implements TypeAdapterFactory {
private int msgType;
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> type) {
return new TypeAdapter<T>() {
public void write(JsonWriter out, T value) throws IOException {
getDelegate(gson, type).write(out, value);
}
public T read(JsonReader in) throws IOException {
TypeToken<T> typeToken = type;
String typeName = type.getRawType().getName();
if (typeName.equals(AbsMsgBody.class.getName())){
typeToken = getTypeToken(msgType);
}
T result = getDelegate(gson, typeToken).read(in);
// 如果是消息类型尝试保存
if (typeName.equals(int.class.getName())){
msgType = (Integer) result;
}
return result;
}
};
}
private <T> TypeToken<T> getTypeToken(int msgType){
switch (msgType){
case Msg.MSG_TYPE_SPEAK:
return (TypeToken<T>) new TypeToken<MsgBodySpeak>(){};
case Msg.MSG_TYPE_COUNT:
return (TypeToken<T>) new TypeToken<MsgBodyCount>(){};
}
throw new IllegalArgumentException();
}
private <T> TypeAdapter<T> getDelegate(Gson gson, TypeToken<T> type){
return gson.getDelegateAdapter(this, type);
}
}
首先如果解析int类型,我们知道它是msgType,把它保存起来(这里偷个懒,因为只有一个int,实际使用需要特殊处理)。
如果解析AbsMsgBody类型,则使用之前保存起来的msgType来决定到底实际是解析成哪种实现类。
然后在使用时注册到gson:
/*
* 反序列化
*/
Msg miscFromJson = null;
try{
miscFromJson = new GsonBuilder()
.registerTypeAdapterFactory(new AbsTypeAdapterFactory())
.create()
.fromJson(json, Msg.class);
}catch (Exception e){
Log.e(TAG, Log.getStackTraceString(e));
}
Log.i(TAG, "decode => " + new Gson().toJson(miscFromJson));
我们传入的json值为:
{
"type": 1,
"body": {
"count": 12
}
}
执行:
decode => {"body":{"count":12},"type":1}
结果是gson成功解析了抽象类对象。
但不要高兴得太早,其实这种方法不太“靠谱”,为什么呢?
1.我们知道gson通过JsonReader对象进行顺序解析,所以,一旦type在body的后面,我们就无法知道type,也无法正确地解析body
2.即使这样,使用这种奇技淫术,我们还要求:type和body中的字段不能在一个类中,比如type和count在一个类,我们不可能解析一个类两次,因为在解析count之前就要知道type
所以结论就是,gson是可以反序列化抽象类的,但是限制太多,真正在项目中基本无法实施,如果有大佬有啥建议可以指点下小老弟。