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

原文出处: 怪盗kidouhtml

注:此系列基于Gson 2.4。前端

本次文章的主要内容:java

  • TypeAdapter
  • JsonSerializer与JsonDeserializer
  • TypeAdapterFactory
  • @JsonAdapter注解
  • TypeAdapter与 JsonSerializer、JsonDeserializer对比
  • TypeAdapter实例
  • 结语
  • 后期预告

1、TypeAdapter

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

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的定义:ide

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 、FieldNamingStrategySinceUntilExpos统统都黯然失色,失去了效果,只会调用咱们实现的UserTypeAdapter.write(JsonWriter, User) 方法,我想怎么写就怎么写。测试

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

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

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。

2、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 )); //结果:100
System.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.classdouble.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);

3、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();

4、@JsonAdapter注解

JsonAdapter相较以前介绍的SerializedNameFieldNamingStrategySinceUntilExpos这几个注解都是比较特殊的,其它的几个都是用在POJO的字段上,而这一个是用在POJO类上的,接收一个参数,且必须是TypeAdpaterJsonSerializerJsonDeserializer这三个其中之一。

上面说JsonSerializerJsonDeserializer都要配合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 仅支持 TypeAdapterTypeAdapterFactory

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的优先级更高。

5、TypeAdapter与 JsonSerializer、JsonDeserializer对比

  TypeAdapter JsonSerializer、JsonDeserializer
引入版本 2.0 1.x
Stream API 支持 不支持*,须要提早生成JsonElement
内存占用 TypeAdapter
效率 TypeAdapter
做用范围 序列化 和 反序列化 序列化 或 反序列化

6、TypeAdapter实例

注:这里的TypeAdapter泛指TypeAdapterJsonSerializerJsonDeserializer
这里的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。不服来战!

本系列:

相关文章
相关标签/搜索