你真的会用Gson吗?Gson使用指南(4)
原文出处: 怪盗kidou
注:此系列基于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方法并最终调用这两个抽象方法。
|
1
2
3
4
5
|
public abstract class TypeAdapter<T> { public abstract void write(JsonWriter out, T value) throws IOException; public abstract T read(JsonReader in) throws IOException; //其它final 方法就不贴出来了,包括`toJson`、`toJsonTree`、`toJson`和`nullSafe`方法。} |
注意:TypeAdapter 以及 JsonSerializer 和 JsonDeserializer 都需要与 GsonBuilder.registerTypeAdapter 示或GsonBuilder.registerTypeHierarchyAdapter配合使用,下面将不再重复说明。
使用示例:
|
1
2
3
4
5
6
7
|
User user = new User("怪盗kidou", 24);user.emailAddress = "ikidou@example.com";Gson gson = new GsonBuilder() //为User注册TypeAdapter .registerTypeAdapter(User.class, new UserTypeAdapter()) .create();System.out.println(gson.toJson(user)); |
UserTypeAdapter的定义:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
public class UserTypeAdapter extends TypeAdapter<User> { @Override public void write(JsonWriter out, User value) throws IOException { out.beginObject(); out.name("name").value(value.name); out.name("age").value(value.age); out.name("email").value(value.email); out.endObject(); } @Override public User read(JsonReader in) throws IOException { User user = new User(); 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(); return user; }} |
当我们为User.class 注册了 TypeAdapter之后,只要是操作User.class 那些之前介绍的@SerializedName 、FieldNamingStrategy、Since、Until、Expos通通都黯然失色,失去了效果,只会调用我们实现的UserTypeAdapter.write(JsonWriter, User) 方法,我想怎么写就怎么写。
再说一个场景,在该系列的第一篇文章就说到了Gson有一定的容错机制,比如将字符串 "24" 转成int 的24,但如果有些情况下给你返了个空字符串怎么办(有人给我评论问到这个问题)?虽然这是服务器端的问题,但这里我们只是做一个示范。
int型会出错是吧,根据我们上面介绍的,我注册一个TypeAdapter 把 序列化和反序列化的过程接管不就行了?
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
Gson gson = new GsonBuilder() .registerTypeAdapter(Integer.class, new TypeAdapter<Integer>() { @Override public void write(JsonWriter out, Integer value) throws IOException { out.value(String.valueOf(value)); } @Override public Integer read(JsonReader in) throws IOException { try { return Integer.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 ,如上面的需求可以用下面的代码。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
Gson gson = new GsonBuilder() .registerTypeAdapter(Integer.class, new JsonDeserializer<Integer>() { @Override public Integer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { try { return json.getAsInt(); } catch (NumberFormatException e) { return -1; } } }) .create();System.out.println(gson.toJson(100)); //结果:100System.out.println(gson.fromJson("\"\"", Integer.class)); //结果-1 |
下面是所有数字都转成序列化为字符串的例子
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
JsonSerializer<Number> numberJsonSerializer = new JsonSerializer<Number>() { @Override public JsonElement serialize(Number src, Type typeOfSrc, JsonSerializationContext context) { return new JsonPrimitive(String.valueOf(src)); }};Gson gson = new GsonBuilder() .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的区别:
| registerTypeAdapter | registerTypeHierarchyAdapter | |
|---|---|---|
| 支持泛型 | 是 | 否 |
| 支持继承 | 否 | 是 |
注:如果一个被序列化的对象本身就带有泛型,且注册了相应的TypeAdapter,那么必须调用Gson.toJson(Object,Type),明确告诉Gson对象的类型。
|
1
2
3
4
5
6
7
8
9
10
11
12
|
Type type = new TypeToken<List<User>>() {}.getType();TypeAdapter typeAdapter = new TypeAdapter<List<User>>() { //略};Gson gson = new GsonBuilder() .registerTypeAdapter(type, typeAdapter) .create();List<User> list = new ArrayList<>();list.add(new User("a",11));list.add(new User("b",22));//注意,多了个type参数String result = gson.toJson(list, type); |
三、TypeAdapterFactory
TypeAdapterFactory,见名知意,用于创建TypeAdapter的工厂类,通过对比Type,确定有没有对应的TypeAdapter,没有就返回null,与GsonBuilder.registerTypeAdapterFactory配合使用。
|
1
2
3
4
5
6
7
8
|
Gson gson = new GsonBuilder() .registerTypeAdapterFactory(new TypeAdapterFactory() { @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) { return null; } }) .create(); |
四、@JsonAdapter注解
JsonAdapter相较之前介绍的SerializedName、FieldNamingStrategy、Since、Until、Expos这几个注解都是比较特殊的,其它的几个都是用在POJO的字段上,而这一个是用在POJO类上的,接收一个参数,且必须是TypeAdpater,JsonSerializer或JsonDeserializer这三个其中之一。
上面说JsonSerializer和JsonDeserializer都要配合GsonBuilder.registerTypeAdapter使用,但每次使用都要注册也太麻烦了,JsonAdapter就是为了解决这个痛点的。
使用方法(以User为例):
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
@JsonAdapter(UserTypeAdapter.class) //加在类上public class User { public User() { } public User(String name, int age) { this.name = name; this.age = age; } public User(String name, int age, String email) { this.name = name; this.age = age; this.email = email; } public String name; public int age; @SerializedName(value = "emailAddress") public String email;} |
使用时不用再使用 GsonBuilder去注册UserTypeAdapter了。
注:@JsonAdapter 仅支持 TypeAdapter或TypeAdapterFactory
|
1
2
3
4
5
|
Gson gson = new Gson();User user = new User("怪盗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对比
| TypeAdapter | JsonSerializer、JsonDeserializer | |
|---|---|---|
| 引入版本 | 2.0 | 1.x |
| Stream API | 支持 | 不支持*,需要提前生成JsonElement |
| 内存占用 | 小 | 比TypeAdapter大 |
| 效率 | 高 | 比TypeAdapter低 |
| 作用范围 | 序列化 和 反序列化 | 序列化 或 反序列化 |
六、TypeAdapter实例
注:这里的TypeAdapter泛指TypeAdapter、JsonSerializer和JsonDeserializer。
这里的TypeAdapter 上面讲了一个自动将 字符串形式的数值转换成int型时可能出现 空字符串的问题,下面介绍一个其它读者的需求:
服务器返回的数据中data字段类型不固定,比如请求成功data是一个List,不成功的时候是String类型,这样前端在使用泛型解析的时候,怎么去处理呢?
其实这个问题的原因主要由服务器端造成的,接口设计时没有没有保证数据的一致性,正确的数据返回姿势:同一个接口任何情况下不得改变返回类型,要么就不要返,要么就返空值,如null、[],{}。
但这里还是给出解决方案:
方案一:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
Gson gson = new GsonBuilder().registerTypeHierarchyAdapter(List.class, new JsonDeserializer<List<?>>() { @Override public List<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { if (json.isJsonArray()){ //这里要自己负责解析了 Gson newGson = new Gson(); return newGson.fromJson(json,typeOfT); }else { //和接口类型不符,返回空List return Collections.EMPTY_LIST; } }}).create(); |
方案二:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
Gson gson = new GsonBuilder().registerTypeHierarchyAdapter(List.class, new JsonDeserializer<List<?>>() { @Override public List<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { if (json.isJsonArray()) { JsonArray array = json.getAsJsonArray(); Type itemType = ((ParameterizedType) typeOfT).getActualTypeArguments()[0]; List list = new ArrayList<>(); for (int i = 0; i < array.size(); i++) { JsonElement element = array.get(i); Object item = context.deserialize(element, itemType); list.add(item); } return list; } else { //和接口类型不符,返回空List return Collections.EMPTY_LIST; } }}).create(); |
要注意的点:
- 必须使用
registerTypeHierarchyAdapter方法,不然对List的子类无效,但如果POJO中都是使用List,那么可以使用registerTypeAdapter。 - 对于是数组的情况,需要创建一个新的Gson,不可以直接使用context,不然gson又会调我们自定义的
JsonDeserializer造成递归调用,方案二没有重新创建Gson,那么就需要提取出List<E>中E的类型,然后分别反序列化适合为E手动注册了TypeAdaper的情况。 - 从效率上推荐方案二,免去重新实例化Gson和注册其它TypeAdapter的过程。
结语
Gson系列总算是完成了,感觉写得越来越差了,我怕我写得太啰嗦,也不能总是大片大片的贴代码,所以可能有的地方写得并不详细,排版也不美观,但都些都不重点,重点是Gson里我们能用上的都一一介绍一遍,只要你确确实实把我这几篇文章上的内容都学会的话,以后Gson上的任何问题都不再是问题,当然可能很多内容对于实际的开发中用的并不多,但下次有什么疑难杂症就难不倒你了。
本系列不提供Demo源码,最重要的是自己实验。
写一篇文章还是要花不少时间和精力,要写示例、调式、组织语言、码字等等,加上关注的人在慢慢的增加的同时既给了我动力也给我不少压力,如有纰漏或者更好的例子都可以和我交流。
后期预告:
之前有人给我评论说 出一点 retrofit 相关内容,我想了想,出是会出,但在此之前我想先出大概3~4篇文章用于介绍 泛型、反射、注解和HTTP 的相关内容,当你确实掌握之后,我打包票你只需要看一遍Retrofit官方教程的代码示例,都不用看其它英文说明,就可以轻松玩转Retrofit。不服来战!
本系列:
- 你真的会用Gson吗?Gson使用指南(1)
- 你真的会用Gson吗?Gson使用指南(2)
- 你真的会用Gson吗?Gson使用指南(3)
- 你真的会用Gson吗?Gson使用指南(4)
- from: http://www.importnew.com/23878.html
你真的会用Gson吗?Gson使用指南(4)的更多相关文章
- 你真的会用Gson吗?Gson使用指南(3)
原文出处: 怪盗kidou 注:此系列基于Gson 2.4. 本次的主要内容: 字段过滤的几种方法 基于@Expose注解 基于版本 基于访问修饰符 基于策略(作者最常用) POJO与JSON的字段映 ...
- 你真的会用Gson吗?Gson使用指南(2)
注:此系列基于Gson 2.4. 上一篇文章 你真的会用Gson吗?Gson使用指南(1) 我们了解了Gson的基础用法,这次我们继续深入了解Gson的使用方法. 本次的主要内容: Gson的流式反序 ...
- 你真的会用Gson吗?Gson使用指南
你真的会用Gson吗?Gson使用指南(一) 你真的会用Gson吗?Gson使用指南(二) 你真的会用Gson吗?Gson使用指南(三) 你真的会用Gson吗?Gson使用指南(四)
- 你真的会用Gson吗?Gson使用指南(1)
JSON (官网) 是一种文本形式的数据交换格式,它比XML更轻量.比二进制容易阅读和编写,调式也更加方便.其重要性不言而喻.解析和生成的方式很多,Java中最常用的类库有:JSON-Java.Gso ...
- 让复杂Json数据和对象自由转换 --- Gson
Gson是谷歌用于对Json操作的库,里面有着强大而又方便的功能,最常用的就是 fromJson():将json数据转化为对象: toJson():将对象转化为json数据! 对于普通的json数据使 ...
- 一起来开发Android的天气软件(四)——使用Gson解析数据
离上一篇文章过去才4.5天,我们赶紧趁热打铁继续完毕该系列的天气软件的开发. 承接上一章的内容使用Volley实现网络的通信.返回给我们的是这一串Json数据{"weatherinfo&qu ...
- Gson的学习与使用
Gson介绍: GSON是Google提供的用来在Java对象和JSON数据之间进行映射的Java类库.可以将一个Json字符转成一个Java对象,或者将一个Java转化为Json字符串. 特点: a ...
- Gson全解析(中)-TypeAdapter的使用
TypeAdapter介绍 前面的Gson全解析(上)中我们理解并分别运用了JsonSerializer和JsonDeserializer进行JSON和java实体类之间的相互转化.这里利用TypeA ...
- [转]用GSON 五招之内搞定任何JSON数组
关于GSON的入门级使用,这里就不提了,如有需要可以看这篇博文 <Google Gson的使用方法,实现Json结构的相互转换> ,写的很好,通俗易懂. 我为什么写这篇文章呢?因为前几晚跟 ...
随机推荐
- 小技巧css解决移动端ios不兼容position:fixed属性,无需插件
移动端开发仿app头部底部固定设置position:fixed,android2.2以上已经实现.但是在ios8以下系统,当小键盘激活时,都会出现位置浮动问题.如图: 如何解决: 查阅资料之后想到一下 ...
- linux之nginx
一.知识点回顾 临时:关闭当前正在运行的 /etc/init.d/iptables stop 永久:关闭开机自启动 chkonfig iptables off ll /var/log/secure # ...
- LeetCode(61):旋转链表
Medium! 题目描述: 给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数. 示例 1: 输入: 1->2->3->4->5->NULL, ...
- 兼容IE8以下,获取className节点的元素(document.getElementsByClassName()兼容写法)。
因为ie8一下不兼容 document.getElementsByClassName() 功能:通过class的名字获取符合条件的元素 ...
- python 全栈开发,Day123(图灵机器人,web录音实现自动化交互问答)
昨日内容回顾 . 百度ai开放平台 . AipSpeech技术,语言合成,语言识别 . Nlp技术,短文本相似度 . 实现一个简单的问答机器人 . 语言识别 ffmpeg (目前所有音乐,视频领域,这 ...
- MVC开发中的常见错误-04-“System.NullReferenceException”类型的异常在 BBFJ.OA.WebApp.dll 中发生,但未在用户代码中进行处理
未将对象引用设置到对象实例,又名空指针异常,伴随程序员开发的一生. 查看详细信息得知: SetUserRoleInfo() 首先想到的是 IBLL.IRoleInfoService RoleInfo ...
- Javascript中类的实现机制(四)
一: 理解类的实现机制 在JavaScript中可以使用function关键字来定义一个“类”,如何为类添加成员.在函数内通过this指针引用的变量或者方法都会成为类的成员,例如:function ...
- 使用共同函数,将PNotify弹出提示框公用
PNotify(http://sciactive.github.io/pnotify/)是个不错的jquery库(好像最新版的pnotify已不需要jquery了). 使用它来实现网站常用的提示框,很 ...
- python的selenium
from selenium import webdriverChromeDriver="C:\Program Files (x86)\Google\Chrome\Application\ch ...
- linux命令简写解释
命令缩写: ls:list(列出目录内容) cd:Change Directory(改变目录) su:switch user 切换用户rpm:redhat package manager 红帽子打包管 ...