使用Gson对多个枚举类型的统一序列化/反序列化处理

关于源码,已经迁移到GitHub上,地址https://github.com/google/gson

也可以直接在这个地址下载http://download.csdn.net/detail/atm008/9556429,已集成下面文章中的方法

-------------------------------------------------------------------------------------以下正文

看文章前请先下载最新Gson代码,地址: https://code.google.com/p/google-gson/downloads/list

这里面包含了jar包和源码,因为这里需要改变一点点源码,所以想要实验的同学还是把源码直接导入工程方便看效果!

最近项目中开始使用Gson来进行对Json的序列化和反序列化,开始用的时候觉得Gson确实很方便,一来简介了代码(特别是在反序列化json的时候),二来使得代码在封装对象的时候更易于阅读。但是后来发现,Gson对于枚举类型的支持并不是我想要的。
既然是增强版前面的原版我就不写了,直接给一个传送门,看看一般大家都是怎么解决枚举类型的,看完后可回来接着看增强版哟。
传送门:http://blog.csdn.net/lcf98/article/details/7691149
http://stackoverflow.com/questions/10297264/gson-how-to-change-output-of-enum
一个是国内的 一个是国外的。
看完上面2个是不是感觉处理enum还是蛮容易的,只要写好对应enum的adapter就可以解决了(有的是通过实现JsonSerializer转换的,这里就不讨论了)。但是!请注意了,一个enum对应一个adapter!那么如果有100个呢?我们知道项目中经常要用到枚举,这样会很方便我们就行类型定义,而不是让人摸不着头脑的1,2,3这样的数字。可是有100个枚举的时候你该怎么办,写100个adapter? 这在工程中是不能容忍的。那么我们该怎么做,我也开始思考这个问题。
一开始有一个想法,写一个基类,让所有枚举的都继承他,但这个想法只维持了2秒,突然想起来枚举是不支持继承的。那么要想统一管理的话只能是写一个接口了,因为枚举是可以实现接口的。
这里我定义了一个IEnum接口:
public interface IEnum {
        int getValue();
        void setValue(int value);
}

写了2个枚举类,实现了IEnum:
Sex:
public enum Sex implements IEnum{
        Man(1),
        Women(2);

        private int value;
        private Sex(int value){
                this.value = value;
        }
        public int getValue(){
                return value;
        }
        @Override
        public void setValue(int value) {
                this.value = value;
        }
}

Degree:
public enum Degree implements IEnum{
        High(1),
        Middle(2),
        Low(3);
        
        private int value;
        private Degree(int value){
                this.value = value;
        }
        public int getValue(){
                return value;
        }
        @Override
        public void setValue(int value) {
                this.value = value;
        }
}


好了,上面的代码简洁易懂,下面是关键。我们来创建自己的Adapter。这里我借鉴国外的设计思想,先写工厂类MyEnumAdapterFactory:
public class MyEnumAdapterFactory implements TypeAdapterFactory {
        
        @Override
        public  TypeAdapter create(Gson gson, TypeToken type) {
                Class rawType = type.getRawType();
                if(rawType.isEnum()){
                        Type[] types = rawType.getGenericInterfaces();
                        for(Type item:types){
                                if(item == IEnum.class){
                                        return new EnumTypeAdapter();
                                }
                        }
                }
                return null;
        }
}

上面代码我解释一下,通过Gson回调过来create函数里面的参数type,可以获取当前对象类型。这里我们抽丝剥见,一步一步看当前对象类型,查看是否是枚举类,并且获取该枚举类所实现的接口,并看其中是否有实现IEnum接口,如果有,那么当前序列化/反序列化的这个对象一定是我们自己定义的枚举类型,故进一步采用我们的EnumTypeAdapter来进行解析。下面是EnumTypeAdapter的代码:
public class EnumTypeAdapter extends TypeAdapter{
        @Override
        public T read(JsonReader in) throws IOException {
                if(in.peek() == JsonToken.NULL){
                        in.nextNull();
                        return null;
                }
                return null;
        }
               
        public Object read(JsonReader in,TypeToken type) throws IOException{
                boolean isEnum = type.getRawType().isEnum();
                if(isEnum){
                        int value = in.nextInt();
                        try {
                                Method valuesMethod = type.getRawType().getMethod("values", null);
                                IEnum[] enumArr = (IEnum[])valuesMethod.invoke(type.getClass(), null);
                                for (IEnum iEnum : enumArr) {
//                                        System.out.println( iEnum );
                                        if(iEnum.getValue() == value){
                                                Log.d("This is a enum ", "value is=====>"+value);
                                                return iEnum;
                                        }
                                }
                                } catch (Exception e) {
                                        e.printStackTrace();
                        }
                }        
                return null;
        }
        @Override
        public void write(JsonWriter out, T value) throws IOException {
                if(value == null){
                        out.nullValue();
                        return;
                }
                        
                if(value instanceof IEnum){
                        out.value(((IEnum)value).getValue());
                }
        }               
}

细心的应该能发现了,我写的adapter相比网上给出的多了一个函数,就是read(JsonReader in,TypeToken type) 。这里我先解释一下2个必须重写的函数。
第一个是read(JsonReader in),这里基本就没怎么写,因为在反序列化的时候,传给我们的JsonReader这个对象除了获取json字符串里面的内容之外并不能获取对应对象的类型,所以可以不作处理,之后会介绍我自己写的read(JsonReader in,TypeToken type)
第二个是 write(JsonWriter out, T value),这里可以看到,Gson把对象回传过来了,也就是value。这是一个泛型,不过没关系,这里我们直接判断类型就可以了,通过判断和强转为IEnum就可以用getValue来获取枚举对应的int值
接下来是重点:我自己写的函数read(JsonReader in,TypeToken type)
首先这是我们加的函数,这不在Gson的控制里面,所以自身是不会主动调用这个函数的,需要我们手动去调用,那么什么时候要调用了。这里就需要我们去修改源码啦~~不过内容很少,找到文件ReflectiveTypeAdapterFactory,再找到里面ReflectiveTypeAdapterFactory.BoundField。大概是82行左右。我们修改其中的read(JsonReader reader, Object value)函数。把它修改成这样:
Object fieldValue = null;
if (typeAdapter instanceof EnumTypeAdapter) {
    fieldValue = ((EnumTypeAdapter) typeAdapter)
            .read(reader, fieldType);
} else {
    fieldValue = typeAdapter.read(reader);
}

if (fieldValue != null || !isPrimitive) {
    field.set(value, fieldValue);
}
     
哈哈 看到了吧。在反序列化的时候当发现我们的adapter为EnumTypeAdapter,我们通过强转,再调用自己写的read函数就好。(源码就只有else里面的内容),因为之前保存了对应对象的type类型,所以这里我们直接把对应的type传到函数里面就好啦~
再看看我们自己的read函数做了哪些事情:
boolean isEnum = type.getRawType().isEnum();
                if(isEnum){
                        int value = in.nextInt();
                        try {
                                Method valuesMethod = type.getRawType().getMethod("values", null);
                                IEnum[] enumArr = (IEnum[])valuesMethod.invoke(type.getClass(), null);
                                for (IEnum iEnum : enumArr) {
//                                        System.out.println( iEnum );
                                        if(iEnum.getValue() == value){
                                                Log.d("This is a enum ", "value is=====>"+value);
                                                return iEnum;
                                        }
                                }
                                } catch (Exception e) {
                                        e.printStackTrace();
                        }
                }        
                return null;


关键词是:反射!对了,这里我们要用到反射的机制,反射对应的函数。首先我们通过传进来的type类型知道了这是实现了IEnum的类,其实就是Enum类型,也就是枚举。我们都知道枚举Enum有一个类方法是"values",他可以列出所有枚举中定义的类型。那么这就好办了,JsonReader可以读取json的数据内容,我们就调用nextInt函数获取Json的值,然后遍历反射方法获取到的所有枚举定义,一一比较其getVaule方法返回值是不是和json中的对应,一旦相等,那么我们返回这个枚举中的定义就好了。

下面我们来实验一下,看看效果如何:新建2个类,一个Teacher 一个Student。具体内容如下:
Student:
public class Student {
        private String stuName;
        private int stuId;
        private Sex mSex;
        private Degree mDegree;
        //...set 和 get方法.....
}

Teacher:
public class Teacher {
        private List students;
        private int studentCount;
        //...set和get方法
}


下面是main方法里面的函数:
Teacher mTeacher = new Teacher();
        for (int i = 0; i < 5; i++) {
                Student stu = new Student();
                stu.setStuId(i);
                stu.setStuName("stu" + i);
                stu.setmDegree(Degree.Low);
                stu.setmSex(Sex.Women);
                mTeacher.getStudents().add(stu);
        }
        mTeacher.setStudentCount(5);

GsonBuilder gb = new GsonBuilder();
gb.registerTypeAdapterFactory(new MyEnumAdapterFactory());
Gson gson = gb.create();

String mJsonStr = gson.toJson(mTeacher);
Log.d("json is====>", mJsonStr);   //打印出转换后的Json值
Teacher teacher = gson.fromJson(mJsonStr, Teacher.class)
Log.d("DeSerilize ok===>", "Sex is:"+ teacher.getStudents().get(0).getmSex()+ " Degree is:"+ teacher.getStudents().get(0).getmDegree());


这里我们把第一个学生的性别和学历打印出来即可
======================================================
第一个序列化log:
{"students":[{"mDegree":3,"mSex":2,"stuName":"stu0","stuId":0},{"mDegree":3,"mSex":2,"stuName":"stu1","stuId":1},{"mDegree":3,"mSex":2,"stuName":"stu2","stuId":2},{"mDegree":3,"mSex":2,"stuName":"stu3","stuId":3},{"mDegree":3,"mSex":2,"stuName":"stu4","stuId":4}],"studentCount":5}


第二个反序列化log:
DeSerilize ok===>(6979): Sex is:Women Degree is:Low


哈哈 大功告成!
2个枚举我们只用了一个adapter,那么100个我们也可以只用一个adapter。是不是通用多了呢~大家有好点子或者别的简便方法可以交流交流,方便大家!

你可能感兴趣的:(使用Gson对多个枚举类型的统一序列化/反序列化处理)