你真的會用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。不服來戰!

本系列:

相關文章
相關標籤/搜索