TypeAdapter
現在輪到TypeAdapter類上場,但考慮到gson默認行爲已足夠強大,加上項目實踐中應用json時場景不會太複雜,所以一般不需要自定義TypeAdapter。TypeAdapter優點是集成了JsonWriter和JsonReader兩個類,定義了一套與gson框架交互的良好接口,同時便於管理編碼和解碼的實現代碼,不至於太零碎。因而在瞭解JsonReader和JsonWriter的使用方法之後,自定義TypeAdapter類來完成特定類的編碼和解碼也就不困難了。
TypeToken
Type listType = new TypeToken<List<String>>() {}.getType();//!!!
List<String> target = new LinkedList<String>();
target.add("blah");
Gson gson = new Gson();
String json = gson.toJson(target, listType);
List<String> target2 = gson.fromJson(json, listType);// !!!
上述代碼在之前的樣例裏看到過,這裏單獨拿出來研究。行尾有!!!的兩行是這裏要特別關注的,原因是看起來很怪異。初次看到第一行代碼的時候感覺非常怪異,很奇怪爲什麼需要設計這樣的API。仔細思索之後,發現設計者真實的目的其實非常簡單,只是爲了提取泛型的類型,原因是Java的泛型對象的類型不能直接通過類型本身獲取到,比如類似List<String>.class的代碼是無法通過編譯,原因和Java泛型的實現機制有關係。
分析TypeToken類的實現代碼,發現GSON的開發者想出了一個比較有意思的實現方法,既然不能直接通過類型本身得到真實的類型對象,那麼就從對象本身得到。而TypeToken就是這一想法的通用實現。TypeToken類本身不能直接實例化,使用時需要定義其子類對象,這時即通過子類對象來獲取其模板類型參數,也就得到了泛型類型的真實類型。
控制縮進
比如面對類似 [{"Qualified Name":"Jackie","age":30,"contact":{"email":"email","phoneno":"phoneno"}},{"Qualified Name":"Jackie Boy","age":1,"contact":{"email":"email","phoneno":"phoneno"}}] 的json格式字符串,對於程序來說理解這樣的字符串毫無壓力,但對於人來說困難就比較大了。那怎麼辦呢?gson有辦法,通過修改gson的默認行爲,可以在輸出成json字符串時,提供縮進使用字符串表現出良好的格式。樣例代碼和縮進後的輸出如下。
final Gson gson = new GsonBuilder().setVersion(1.1).setPrettyPrinting().create();// 調用setPrettyPrinting方法,改變gson對象的默認行爲
如下即是輸出
[
{
"Qualified Name": "Jackie",
"age": 30,
"contact": {
"email": "email",
"phoneno": "phoneno"
}
},
{
"Qualified Name": "Jackie Boy",
"age": 1,
"contact": {
"email": "email",
"phoneno": "phoneno"
}
}
]
這個特性很有意思,但項目實際開發的時候可能意義不大,畢竟換行、縮進都是佔用空間的。日誌裏輸出json格式的字符串時,倒是可以考慮寫入調整過格式後的json字符串,在事後分析日誌時方便閱讀,給運維同事減輕負擔。
輸出空引用
基於已有的使用經驗,如果不做對字段進行特別的處理,對於引用類型的成員,如果取值爲null時,編碼後將不會出現在json字符串中,這是gson的默認行爲。但通過修改gson的默認行爲,可以將取值爲null的字段也輸出到json字符串中。樣例代碼如下,爲了方便查看,還設置了縮進。
final Gson gson = new GsonBuilder().setVersion(1.1).serializeNulls().setPrettyPrinting().create();// 調用serializeNulls方法,改變gson對象的默認行爲
如下是輸出
[
{
"Qualified Name": "Jackie",
"age": 30,
"contact": {
"email": "email",
"phoneno": "phoneno"
},
"address": null,// address和location被原樣輸出
"location": null
},
{
"Qualified Name": "Jackie Boy",
"age": 1,
"contact": {
"email": "email",
"phoneno": "phoneno"
},
"address": null,
"location": null
}
]
// 如下是類定義
@Data
class Person {
@Since(1.0)
@SerializedName("Qualified Name")
private String name;
@Since(1.1)
private int age;
@Since(1.0)
private Map<String, Object> contact;
private String address;
private String location;
public Person() {
contact = new HashMap<String, Object>();
}
public void putValue(final String key, final Object value) {
contact.put(key, value);
}
}
Gson判斷Java Bean的字段是否需要格式化爲json的方法
這是一個好問題。有這麼幾種可能:
- 從Java Bean類的元數據中提取成員列表,然後根據各個成員的類型來生成對象的json表示;
- 可以提取符合getter/settter方法命名規範的公有方法列表,然後依據這個列表來生成對象的json表示;
設計如下的樣例代碼,可以檢驗一下剛纔的猜測。
import java.util.HashMap;
import java.util.Map;
import lombok.AccessLevel;
import lombok.Data;
import lombok.Getter;
import com.google.gson.Gson;
public class JsonTest {
public static void main(final String[] args) {
final Gson gson = new Gson();
final Person jack1 = new Person();
jack1.setAge(30);
jack1.setName("Jackie");
jack1.putValue("email", "email");
jack1.putValue("phoneno", "phoneno");
final String json = gson.toJson(jack1);
System.out.println(json);
final Gson gson2 = new Gson();
final Person p = gson2.fromJson(json, Person.class);
System.out.println(p);
}
}
@Data
class Person {
private String name;
@Getter(AccessLevel.NONE)
// @Setter(AccessLevel.NONE)
private int age;
private Map<String, Object> contact;
private String address;
private String location;
public Person() {
age = 20;
contact = new HashMap<String, Object>();
}
public void putValue(final String key, final Object value) {
contact.put(key, value);
}
/*
public int getAGe() {
return age;
}
public int getAge() {
return 90;
}
*/
}
樣例代碼的輸出如下。
{"name":"Jackie","age":30,"contact":{"email":"email","phoneno":"phoneno"}}
Person(name=Jackie, age=30, contact={email=email, phoneno=phoneno}, address=null, location=null)
前述代碼主要是在驗證成員的公有方法對gson爲對象生成json字符串時的影響。比如通過使用註解,控制lombok不爲Person類的成員age生成getter方法,或者寫一個命名不合要求的訪問方法,或者寫一個命名符合要求,但是取值與成員age的值無關的公有getter方法。經驗證,這幾種修改方法對最終的結果沒有任何影響,因而從上面的樣例代碼可以得出結論,gson在爲Bean對象生成json時,並沒有依賴類定義中的公有方法。假如gson直接使用Bean元數據中的成員信息來直接生成json的話,暫時沒有想到怎樣設計樣例代碼來檢驗,所以只好單步跟蹤gson在生成json時的代碼。最終跟蹤到了如下的一段代碼。
// 如下代碼來自於gson 2.2.4,版權歸作者所有
Field[] fields = raw.getDeclaredFields();
for (Field field : fields) {
boolean serialize = excludeField(field, true);
boolean deserialize = excludeField(field, false);
if (!serialize && !deserialize) {
continue;
}
field.setAccessible(true);
Type fieldType = $Gson$Types.resolve(type.getType(), raw, field.getGenericType());
BoundField boundField = createBoundField(context, field, getFieldName(field),
TypeToken.get(fieldType), serialize, deserialize);
BoundField previous = result.put(boundField.name, boundField);
if (previous != null) {
throw new IllegalArgumentException(declaredType
+ " declares multiple JSON fields named " + previous.name);
}
}
這段代碼在類com.google.gson.internal.bind.ReflectiveTypeAdapterFactory的方法getBoundFields中,不相關的部分沒有列出來。從這段代碼可以看出gson直接讀取JavaBean的成員信息來完成json的生成,並沒有校驗成員是否定義了get/set方法。