原文出處: 怪盜kidouhtml
注:此係列基於Gson 2.4。前端
本次文章的主要內容:java
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
、FieldNamingStrategy
、Since
、Until
、Expos
統統都黯然失色,失去了效果,只會調用咱們實現的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。
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.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,見名知意,用於建立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
相較以前介紹的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 | |
---|---|---|
引入版本 | 2.0 | 1.x |
Stream API | 支持 | 不支持*,須要提早生成JsonElement |
內存佔用 | 小 | 比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
。JsonDeserializer
形成遞歸調用,方案二沒有從新建立Gson,那麼就須要提取出List<E>中E的類型,而後分別反序列化適合爲E手動註冊了TypeAdaper的狀況。Gson系列總算是完成了,感受寫得愈來愈差了,我怕我寫得太囉嗦,也不能老是大片大片的貼代碼,因此可能有的地方寫得並不詳細,排版也不美觀,但都些都不重點,重點是Gson裏咱們能用上的都一一介紹一遍,只要你確確實實把我這幾篇文章上的內容都學會的話,之後Gson上的任何問題都再也不是問題,固然可能不少內容對於實際的開發中用的並很少,但下次有什麼疑難雜症就難不倒你了。
本系列不提供Demo源碼,最重要的是本身實驗。
寫一篇文章仍是要花很多時間和精力,要寫示例、調式、組織語言、碼字等等,加上關注的人在慢慢的增長的同時既給了我動力也給我很多壓力,若有紕漏或者更好的例子均可以和我交流。
以前有人給我評論說 出一點 retrofit 相關內容,我想了想,出是會出,但在此以前我想先出大概3~4篇文章用於介紹 泛型、反射、註解和HTTP 的相關內容,當你確實掌握以後,我打包票你只須要看一遍Retrofit官方教程的代碼示例,都不用看其它英文說明,就能夠輕鬆玩轉Retrofit。不服來戰!