GSON使用的學習筆記,進階篇(三)

        本篇筆記內容比較雜亂,沒有專門去整理。

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的方法

        這是一個好問題。有這麼幾種可能:

  1. 從Java Bean類的元數據中提取成員列表,然後根據各個成員的類型來生成對象的json表示;
  2. 可以提取符合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方法。
發佈了70 篇原創文章 · 獲贊 52 · 訪問量 28萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章