gson反序列化抽象类的尝试_gson反序列化,对象为抽象类-程序员宅基地

技术标签: android  

本文主要尝试对类似报文结构的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是可以反序列化抽象类的,但是限制太多,真正在项目中基本无法实施,如果有大佬有啥建议可以指点下小老弟。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/starry_eve/article/details/108285320

智能推荐

披着函数外衣的关键字sizeof-程序员宅基地

文章浏览阅读857次。通过这篇文章可以让大家更好的理解sizeof而不至于闹出sizeof是函数的笑话。

学习笔记6(opencv+python阈值分割(最大熵))_python 最大熵阈值法-程序员宅基地

文章浏览阅读2.9k次,点赞4次,收藏37次。@ 图像阈值分割(最大熵方法)老规矩,看相关函数(哈哈,没有啥函数)步骤1.进行归一化直方图2.累加概率直方图3.求出各个灰度级的熵4.计算最大熵时的阈值计算公式1.normHist为归一化的直方图,这里不做介绍2.累加概率直方图3.求出各个灰度级的熵4.计算最大熵时的阈值计算:f(t)=f1(t)+f2(t)最大化的t值,该值即为得到的阈值,即thresh=argmax(f(t))上代码#相关包import numpy as npimport cv2import im_python 最大熵阈值法

electron cookie实现记住密码_electron 记住密码-程序员宅基地

文章浏览阅读2.2w次,点赞3次,收藏16次。使用electron开发时,实现记住密码功能。这个功能使用比较常见,electron也提供了session的模块来支持。当然session模块还有更多的用处。关于更多的session模块的使用,请查看Electron文档。还有很多其他的实现方式,比如使用一个json文件来存放用户名密码等。_electron 记住密码

在网页制作中增加微信QQ微博等ICO-font图标字体符号-程序员宅基地

文章浏览阅读809次。在网页制作中增加微信QQ微博等ICO-font图标字体符号window._bd_share_config={"common":{"bdSnsKey":{},"bdText":"","bdMini":"2","bdMiniList":false,"bdPic":"","bdStyle":"0","bdSize":"16"},"share":{}};with(document)..._weibo wechat font

dwarf程序_dwarf.h-程序员宅基地

文章浏览阅读1.2k次,点赞2次,收藏3次。解析全局变量,函数,局部变量并按一定格式保存 点击(此处)折叠或打开 #include sys/types.h> #include sys/stat.h> #include fcntl.h> #include stdlib.h>_dwarf.h

随便推点

ssh数据源(c3p0+was)-程序员宅基地

文章浏览阅读114次。ssh web 应用使用c3p0数据库连接池ibm 给出的实例说明 将ssh web应用程序从tomcat迁移到websphere(was)WAS 中数据源的配置使用及其常见问题开源DBCP、C3P0、Proxool 、 BoneCP连接池的比较 ..._was数据源 ssh

实现点击通知栏发送广播控制音乐的播放、暂停、上一曲、下一曲_adb 发送上下一曲按键-程序员宅基地

文章浏览阅读3.4k次。Intent intent = new Intent(this,Main2Activity.class); PendingIntent pending = PendingIntent.getActivity(this, 0, intent, 0); builder.setContentIntent(pending); builder.setOngoi_adb 发送上下一曲按键

C# .NetCore知识点-程序员宅基地

文章浏览阅读6.4k次,点赞11次,收藏78次。1、.netCore的执行过程2、如何在controller中注入service?在Config Services方法中配置这个service在Controller的构造函数中 添加这个依赖注入3、.netCore比.net更具优势的地方是什么?跨平台,可以运行在 Windows 、Linux 和 MAC 系统上对框架本身安装没有依赖,所有依赖都和程序本身在一起.netCore处理请求的效率更高,进而可以处理更多的请求具有更多的安装配置方法4、.netCore主要的特性有哪些?依赖注入_.netcore

c++实现--考试排名_++编程考试使用的实时提交系统,具有即时获得成绩排名的特点。它的功能是怎么实现-程序员宅基地

文章浏览阅读2.5k次。题意程序设计思维作业和实验使用的实时评测系统,具有及时获得成绩排名的特点,那它的功能是怎么实现的呢?我们千辛万苦怼完了不忍直视的程序并提交以后,评测系统要么返回AC,要么是返回各种其他的错误,不论是怎样的错法,它总会给你记上一笔,表明你曾经在这儿被坑过,而当你历经千辛终将它AC之后,它便会和你算笔总账,表明这题共错误提交了几次。在岁月的长河中,你通过的题数虽然越来越多,但通过每题时你所共花去..._++编程考试使用的实时提交系统,具有即时获得成绩排名的特点。它的功能是怎么实现

phpmywind调用方法大全-程序员宅基地

文章浏览阅读1.2k次。头部文件调用<?php require_once('header.php'); ?>    底部文件调用<?php require_once('footer.php'); ?>关于我们图片调用<?php $row = $dosql->GetOne("SELECT * FROM `#@__info` W..._phpmywind首页关于我们怎么调用

巨坑-读取不到Nacos配置中心的内容_linux中部署jar包读取不到nacos配置文件的内容-程序员宅基地

文章浏览阅读2.2k次。[巨坑]读取不到Nacos配置中心的内容!这真的太逆天了,我服了这是出错的配置:报错内容:确定其他都没有问题,但就是读不到配置。这是正确的配置:excuse me ??? 换了无数个SpringCloud SpringBoot 和 Nacos的版本,windows平台linux平台全部试过去,结果就是因为这个写错了(而且我还不知道这到底是错哪了,有什么区别…)浪费了我三个小时时间,大家有则改之,无则加勉。..._linux中部署jar包读取不到nacos配置文件的内容

推荐文章

热门文章

相关标签