Gson使用指南

转自:http://www.jianshu.com/p/3108f1e44155?nomobile=yes


该系列其它文章

你真的会用Gson吗?Gson使用指南(一)

你真的会用Gson吗?Gson使用指南(二)

你真的会用Gson吗?Gson使用指南(三)

你真的会用Gson吗?Gson使用指南(四)

注:此系列基于Gson 2.4。

本次文章的主要内容:

TypeAdapter

JsonSerializer与JsonDeserializer

TypeAdapterFactory

@JsonAdapter注解

TypeAdapter与 JsonSerializer、JsonDeserializer对比

TypeAdapter实例

结语

后期预告

一、TypeAdapter

TypeAdapter是Gson自2.0(源码注释上说的是2.1)开始版本提供的一个抽象类,用于接管某种类型的序列化和反序列化过程,包含两个注要方法write(JsonWriter,T)和read(JsonReader)其它的方法都是final方法并最终调用这两个抽象方法。

publicabstractclassTypeAdapter{publicabstractvoidwrite(JsonWriter out, T value)throwsIOException;publicabstractTread(JsonReader in)throwsIOException;//其它final 方法就不贴出来了,包括`toJson`、`toJsonTree`、`toJson`和`nullSafe`方法。}

注意:TypeAdapter 以及 JsonSerializer 和 JsonDeserializer 都需要与GsonBuilder.registerTypeAdapter示或GsonBuilder.registerTypeHierarchyAdapter配合使用,下面将不再重复说明。

使用示例:

User user =newUser("怪盗kidou",24);user.emailAddress ="ikidou@example.com";Gson gson =newGsonBuilder()//为User注册TypeAdapter.registerTypeAdapter(User.class,newUserTypeAdapter())        .create();System.out.println(gson.toJson(user));

UserTypeAdapter的定义:

publicclassUserTypeAdapterextendsTypeAdapter{@Overridepublicvoidwrite(JsonWriter out, User value)throwsIOException{        out.beginObject();        out.name("name").value(value.name);        out.name("age").value(value.age);        out.name("email").value(value.email);        out.endObject();    }@OverridepublicUserread(JsonReader in)throwsIOException{        User user =newUser();        in.beginObject();while(in.hasNext()) {switch(in.nextName()) {case"name":                    user.name = in.nextString();break;case"age":                    user.age = in.nextInt();break;case"email":case"email_address":case"emailAddress":                    user.email = in.nextString();break;            }        }        in.endObject();returnuser;    }}

当我们为User.class注册了TypeAdapter之后,只要是操作User.class那些之前介绍的@SerializedName、FieldNamingStrategy、Since、Until、Expos通通都黯然失色,失去了效果,只会调用我们实现的UserTypeAdapter.write(JsonWriter, User)方法,我想怎么写就怎么写。

再说一个场景,在该系列的第一篇文章就说到了Gson有一定的容错机制,比如将字符串"24"转成int 的24,但如果有些情况下给你返了个空字符串怎么办(有人给我评论问到这个问题)?虽然这是服务器端的问题,但这里我们只是做一个示范。

int型会出错是吧,根据我们上面介绍的,我注册一个TypeAdapter 把 序列化和反序列化的过程接管不就行了?

Gson gson =newGsonBuilder()        .registerTypeAdapter(Integer.class,newTypeAdapter() {@Overridepublicvoidwrite(JsonWriter out, Integer value)throwsIOException{                out.value(String.valueOf(value));            }@OverridepublicIntegerread(JsonReader in)throwsIOException{try{returnInteger.parseInt(in.nextString());                }catch(NumberFormatException e) {return-1;                }            }        })        .create();System.out.println(gson.toJson(100));// 结果:"100"System.out.println(gson.fromJson("\"\"",Integer.class));// 结果:-1

注:测试空串的时候一定是"\"\""而不是"",""代表的是没有json串,"\"\""才代表json里的""。

你说这一接管就要管两样好麻烦呀,我明明只想管序列化(或反列化)的过程的,另一个过程我并不关心,难道没有其它更简单的方法么? 当然有!就是接下来要介绍的JsonSerializer与JsonDeserializer

二、JsonSerializer与JsonDeserializer

JsonSerializer和JsonDeserializer不用像TypeAdapter一样,必须要实现序列化和反序列化的过程,你可以据需要选择,如只接管序列化的过程就用JsonSerializer,只接管反序列化的过程就用JsonDeserializer,如上面的需求可以用下面的代码。

Gson gson =newGsonBuilder()        .registerTypeAdapter(Integer.class,newJsonDeserializer() {            @OverridepublicIntegerdeserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)throws JsonParseException{try{returnjson.getAsInt();                }catch(NumberFormatException e) {return-1;                }            }        })        .create();System.out.println(gson.toJson(100));//结果:100System.out.println(gson.fromJson("\"\"", Integer.class));//结果-1

下面是所有数字都转成序列化为字符串的例子

JsonSerializer numberJsonSerializer =newJsonSerializer() {@OverridepublicJsonElementserialize(Number src, Type typeOfSrc, JsonSerializationContext context){returnnewJsonPrimitive(String.valueOf(src));    }};Gson gson =newGsonBuilder()        .registerTypeAdapter(Integer.class, numberJsonSerializer)        .registerTypeAdapter(Long.class, numberJsonSerializer)        .registerTypeAdapter(Float.class, numberJsonSerializer)        .registerTypeAdapter(Double.class, numberJsonSerializer)        .create();System.out.println(gson.toJson(100.0f));//结果:"100.0"

注:registerTypeAdapter必须使用包装类型,所以int.class,long.class,float.class和double.class是行不通的。同时不能使用父类来替上面的子类型,这也是为什么要分别注册而不直接使用Number.class的原因。

上面特别说明了registerTypeAdapter不行,那就是有其它方法可行咯?当然!换成registerTypeHierarchyAdapter就可以使用Number.class而不用一个一个的当独注册啦!

registerTypeAdapter与registerTypeHierarchyAdapter的区别:

registerTypeAdapterregisterTypeHierarchyAdapter

支持泛型是否

支持继承否是

注:如果一个被序列化的对象本身就带有泛型,且注册了相应的TypeAdapter,那么必须调用Gson.toJson(Object,Type),明确告诉Gson对象的类型。

Type type =newTypeToken>() {}.getType();TypeAdapter typeAdapter =newTypeAdapter>() {//略};Gson gson =newGsonBuilder()        .registerTypeAdapter(type, typeAdapter)        .create();List list =newArrayList<>();list.add(newUser("a",11));list.add(newUser("b",22));//注意,多了个type参数String result = gson.toJson(list, type);

三、TypeAdapterFactory

TypeAdapterFactory,见名知意,用于创建TypeAdapter的工厂类,通过对比Type,确定有没有对应的TypeAdapter,没有就返回null,与GsonBuilder.registerTypeAdapterFactory配合使用。

Gsongson =newGsonBuilder()    .registerTypeAdapterFactory(newTypeAdapterFactory() {@Overridepublic TypeAdapter create(Gsongson,TypeTokentype) {returnnull;        }    })    .create();

四、@JsonAdapter注解

JsonAdapter相较之前介绍的SerializedName、FieldNamingStrategy、Since、Until、Expos这几个注解都是比较特殊的,其它的几个都是用在POJO的字段上,而这一个是用在POJO类上的,接收一个参数,且必须是TypeAdpater,JsonSerializer或JsonDeserializer这三个其中之一。

上面说JsonSerializer和JsonDeserializer都要配合GsonBuilder.registerTypeAdapter使用,但每次使用都要注册也太麻烦了,JsonAdapter就是为了解决这个痛点的。

使用方法(以User为例):

@JsonAdapter(UserTypeAdapter.class)//加在类上publicclassUser{publicUser(){    }publicUser(String name,intage){this.name = name;this.age = age;    }publicUser(String name,intage, String email){this.name = name;this.age = age;this.email = email;    }publicString name;publicintage;@SerializedName(value ="emailAddress")publicString email;}

使用时不用再使用GsonBuilder去注册UserTypeAdapter了。

注:@JsonAdapter仅支持TypeAdapter或TypeAdapterFactory

Gson gson =newGson();User user =newUser("怪盗kidou",24,"ikidou@example.com");System.out.println(gson.toJson(user));//结果:{"name":"怪盗kidou","age":24,"email":"ikidou@example.com"}//为区别结果,特意把email字段与@SerializedName注解中设置的不一样

注意:JsonAdapter的优先级比GsonBuilder.registerTypeAdapter的优先级更高。

五、TypeAdapter与 JsonSerializer、JsonDeserializer对比

TypeAdapterJsonSerializer、JsonDeserializer

引入版本2.01.x

Stream API支持不支持*,需要提前生成JsonElement

内存占用小比TypeAdapter大

效率高比TypeAdapter低

作用范围序列化反序列化序列化反序列化

六、TypeAdapter实例

注:这里的TypeAdapter泛指TypeAdapter、JsonSerializer和JsonDeserializer。

这里的TypeAdapter 上面讲了一个自动将 字符串形式的数值转换成int型时可能出现 空字符串的问题,下面介绍一个其它读者的需求:

服务器返回的数据中data字段类型不固定,比如请求成功data是一个List,不成功的时候是String类型,这样前端在使用泛型解析的时候,怎么去处理呢?

其实这个问题的原因主要由服务器端造成的,接口设计时没有没有保证数据的一致性,正确的数据返回姿势:同一个接口任何情况下不得改变返回类型,要么就不要返,要么就返空值,如null、[],{}

但这里还是给出解决方案:

方案一:

Gson gson =newGsonBuilder().registerTypeHierarchyAdapter(List.class,newJsonDeserializer>() {    @OverridepublicList deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {if(json.isJsonArray()){//这里要自己负责解析了Gson newGson =newGson();returnnewGson.fromJson(json,typeOfT);        }else{//和接口类型不符,返回空ListreturnCollections.EMPTY_LIST;        }    }}).create();

方案二:

Gson gson =newGsonBuilder().registerTypeHierarchyAdapter(List.class,newJsonDeserializer>() {    @OverridepublicList deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {if(json.isJsonArray()) {            JsonArrayarray= json.getAsJsonArray();            Type itemType = ((ParameterizedType) typeOfT).getActualTypeArguments()[0];Listlist=newArrayList<>();for(int i =0; i

要注意的点:

必须使用registerTypeHierarchyAdapter方法,不然对List的子类无效,但如果POJO中都是使用List,那么可以使用registerTypeAdapter。

对于是数组的情况,需要创建一个新的Gson,不可以直接使用context,不然gson又会调我们自定义的JsonDeserializer造成递归调用,方案二没有重新创建Gson,那么就需要提取出List中E的类型,然后分别反序列化适合为E手动注册了TypeAdaper的情况。

从效率上推荐方案二,免去重新实例化Gson和注册其它TypeAdapter的过程。

结语

Gson系列总算是完成了,感觉写得越来越差了,我怕我写得太啰嗦,也不能总是大片大片的贴代码,所以可能有的地方写得并不详细,排版也不美观,但都些都不重点,重点是Gson里我们能用上的都一一介绍一遍,只要你确确实实把我这几篇文章上的内容都学会的话,以后Gson上的任何问题都不再是问题,当然可能很多内容对于实际的开发中用的并不多,但下次有什么疑难杂症就难不倒你了。

本系列不提供Demo源码,最重要的是自己实验。

写一篇文章还是要花不少时间和精力,要写示例、调式、组织语言、码字等等,加上关注的人在慢慢的增加的同时既给了我动力也给我不少压力,如有纰漏或者更好的例子都可以和我交流。

后期预告:

之前有人给我评论说 出一点 retrofit 相关内容,我想了想,出是会出,但在此之前我想先出大概3~4篇文章用于介绍泛型、反射、注解和HTTP的相关内容,当你确实掌握之后,我打包票你只需要看一遍Retrofit官方教程的代码示例,都不用看其它英文说明,就可以轻松玩转Retrofit。不服来战!

文/怪盗kidou(简书作者)

原文链接:http://www.jianshu.com/p/3108f1e44155

著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容