关于源码,已经迁移到GitHub上,地址https://github.com/google/gson
也可以直接在这个地址下载,已集成下面文章中的方法
-------------------------------------------------------------------------------------以下正文
看文章前请先下载最新Gson代码,地址: https://code.google.com/p/google-gson/downloads/list
这里面包含了jar包和源码,因为这里需要改变一点点源码,所以想要实验的同学还是把源码直接导入工程方便看效果!
最近项目中开始使用Gson来进行对Json的序列化和反序列化,开始用的时候觉得Gson确实很方便,一来简介了代码(特别是在反序列化json的时候),二来使得代码在封装对象的时候更易于阅读。但是后来发现,Gson对于枚举类型的支持并不是我想要的。
既然是增强版前面的原版我就不写了,直接给一个传送门,看看一般大家都是怎么解决枚举类型的,看完后可回来接着看增强版哟。
传送门:
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 <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) { Class<? super T> rawType = type.getRawType(); if(rawType.isEnum()){ Type[] types = rawType.getGenericInterfaces(); for(Type item:types){ if(item == IEnum.class){ return new EnumTypeAdapter<T>(); } } } return null; } }
上面代码我解释一下,通过Gson回调过来create函数里面的参数type,可以获取当前对象类型。这里我们抽丝剥见,一步一步看当前对象类型,查看是否是枚举类,并且获取该枚举类所实现的接口,并看其中是否有实现IEnum接口,如果有,那么当前序列化/反序列化的这个对象一定是我们自己定义的枚举类型,故进一步采用我们的EnumTypeAdapter来进行解析。下面是EnumTypeAdapter的代码:
public class EnumTypeAdapter<T> extends TypeAdapter<T>{ @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<T> 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<T> type) 。这里我先解释一下2个必须重写的函数。
第一个是read(JsonReader in),这里基本就没怎么写,因为在反序列化的时候,传给我们的JsonReader这个对象除了获取json字符串里面的内容之外并不能获取对应对象的类型,所以可以不作处理,之后会介绍我自己写的read(JsonReader in,TypeToken<T> type)
第二个是 write(JsonWriter out, T value),这里可以看到,Gson把对象回传过来了,也就是value。这是一个泛型,不过没关系,这里我们直接判断类型就可以了,通过判断和强转为IEnum就可以用getValue来获取枚举对应的int值
接下来是重点:我自己写的函数read(JsonReader in,TypeToken<T> 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<Student> 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。是不是通用多了呢~大家有好点子或者别的简便方法可以交流交流,方便大家!