Gson:我爸是 Google
01、前世今生
我叫 Gson,是一款開源的 Java 庫,主要用途為序列化 Java 對象為 JSON 字符串,或反序列化 JSON 字符串成 Java 對象。從我的名字上,就可以看得出一些端倪,我并非籍籍無名,我出身貴族,我爸就是 Google,市值富可敵國。
當然了,作為一個聰明人,我是有自知之明的,我在我爸眼里,我并不是最閃耀的那顆星。
我來到這個世上,純屬一次意外,反正我爸是這樣對我說的,他總說我是從河邊撿回來的,雖然我一直不太相信。對于這件事,我向我媽確認過,她聽完笑得合不攏嘴,說我太天真。
長大后,我喜歡四處闖蕩,因此結識了不少同行,其中就有 Jackson 和 Fastjson。
說起 Jackson,我總能第一時間想到 MJ,那個被上帝帶走的流行天王。Jackson 在 GitHub 上有 6.1k 的 star,雖然他的粉絲數沒我多,但作為 Spring Boot 的默認 JSON 解析器,我非常地尊重他。
Fastjson 來自神秘的東方,雖然爆出過一些嚴重的漏洞,但這并不妨礙他成為最受歡迎的 JSON 解析器,他的粉絲數比我還要多,盡管我已經有超過 18K 的 star。
外人總說我們是競爭對手,但我必須得告訴他們,我們仨的關系,好到就差穿同一條內褲了。
我們各有優勢,Jackson 在運行時占用的內存較少,Fastjson 的速度更快,而我,可以處理任意的 Java 對象,甚至在沒有源代碼的情況下。另外,我對泛型的支持也更加的友好。
02、添加依賴
在使用我的 API 之前,需要先把我添加到項目當中,推薦使用 Maven 和 Gradle 兩種形式。
Maven:
- <dependency>
- <groupId>com.google.code.gson</groupId>
- <artifactId>gson</artifactId>
- <version>2.8.6</version>
- </dependency>
Gradle:
- dependencies {
- implementation 'com.google.code.gson:gson:2.8.6'
- }
PS:Gradle 是一個基于 Apache Ant 和 Apache Maven 概念的項目自動化建構工具。Gradle 構建腳本使用的是 Groovy 或 Kotlin 的特定領域語言來編寫的,而不是傳統的 XML。
03、性能表現
不是我覺得,是真的,通過大量的測試證明,我在處理 JSON 的時候性能還是很牛逼的。
測試環境:雙核,8G 內存,64 位的 Ubuntu 操作系統(以桌面應用為主的 Linux 發行版)
測試結果:
1)在反序列化 25M 以上的字符串時沒有出現過任何問題。
2)可以序列化 140 萬個對象的集合。
3)可以反序列化包含 87000 個對象的集合。
4)將字節數組和集合的反序列化限制從 80K 提高到 11M 以上。
測試用例我已經幫你寫好了,放在 GitHub 上,如果你不相信的話,可以驗證一下。
https://github.com/google/gson/blob/master/gson/src/test/java/com/google/gson/metrics/PerformanceTest.java
04、使用指南
不是我自吹自擂,是真的,我還是挺好用的,上手難度幾乎為零。如果你不相信話,可以來試試。
我有一個女朋友,她的名字和我一樣,也叫 Gson,我的主要功能都由她來提供。你可以通過 new Gson() 的這種簡單粗暴的方式創建她,也可以打電話給一個叫 GsonBuilder 的老板,讓他郵寄一個復刻版過來,真的,我不騙你。
先來看一個序列化的例子。
- Gson gson = new Gson();
- System.out.println(gson.toJson(18));
- System.out.println(gson.toJson("沉默"));
- System.out.println(gson.toJson(new Integer(18)));
- int[] values = { 18,20 };
- System.out.println(gson.toJson(values));
在我女朋友的幫助下,你可以將基本數據類型 int、字符串類型 String、包裝器類型 Integer、int 數組等等作為參數,傳遞給 toJson() 方法,該方法將會返回一個 JSON 形式的字符串。
來看一下輸出結果:
- 18
- "沉默"
- 18
- [18,20]
再來看一下反序列化的例子。
- Gson gson = new Gson();
- int one = gson.fromJson("1", int.class);
- Integer two = gson.fromJson("2", Integer.class);
- Boolean false1 = gson.fromJson("false", Boolean.class);
- String str = gson.fromJson("\"王二\"", String.class);
- String[] anotherStr = gson.fromJson("[\"沉默\",\"王二\"]", String[].class);
- System.out.println(one);
- System.out.println(two);
- System.out.println(false1);
- System.out.println(str);
- System.out.println(Arrays.toString(anotherStr));
toJson() 方法用于序列化,對應的,fromJson() 方法用于反序列化。不過,你需要在反序列化的時候,指定參數的類型,是 int 還是 Integer,是 Boolean 還是 String,或者 String 數組。
來看一下輸出結果:
- 1
- 2
- false
- 王二
- [沉默, 王二]
上面的例子都比較簡單,還體現不出來我的威力。
下面,我們來自定義一個類:
- public class Writer {
- private int age = 18;
- private String name = "王二";
- private transient int sex = 1;
- }
然后,我們來將其序列化:
- Writer writer = new Writer();
- Gson gson = new Gson();
- String json = gson.toJson(writer);
- System.out.println(json);
用法和之前一樣簡單,來看一下輸出結果:
- {"age":18,"name":"王二"}
同樣,可以將結果反序列化:
- Writer writer1 = gson.fromJson(json, Writer.class);
這里有一些注意事項,我需要提醒你。
1)推薦使用 private 修飾字段。
2)不需要使用任何的注解來表明哪些字段需要序列化,哪些字段不需要序列化。默認情況下,包括所有的字段,以及從父類繼承過來的字段。
3)如果一個字段被 transient 關鍵字修飾的話,它將不參與序列化。
4)如果一個字段的值為 null,它不會在序列化后的結果中顯示。
5)JSON 中缺少的字段將在反序列化后設置為默認值,引用數據類型的默認值為 null,數字類型的默認值為 0,布爾值默認為 false。
接下來,來看一個序列化集合的例子。
- List<String> list =new ArrayList<>();
- list.add("好好學習");
- list.add("天天向上");
- String json = gson.toJson(list);
結果如下所示:
- ["好好學習","天天向上"]
反序列化的時候,也很簡單。
- List<String> listResult = gson.fromJson(json,List.class);
結果如下所示:
- [好好學習, 天天向上]
我女朋友是一個很細心也很貼心的人,在你調用 toJson() 方法進行序列化的時候,她會先判 null,防止拋出 NPE,再通過 getClass() 獲取參數的類型,然后進行序列化。
- public String toJson(Object src) {
- if (src == null) {
- return toJson(JsonNull.INSTANCE);
- }
- return toJson(src, src.getClass());
- }
但是呢?對于泛型來說,getClass() 的時候會丟掉參數化類型。來看下面這個例子。
- public class Foo<T> {
- T value;
- public void set(T value) {
- this.value = value;
- }
- public T get() {
- return value;
- }
- public static void main(String[] args) {
- Gson gson = new Gson();
- Foo<Bar> foo = new Foo<Bar>();
- Bar bar = new Bar();
- foo.set(bar);
- String json = gson.toJson(foo);
- }
- }
- class Bar{
- private int age = 10;
- private String name = "圖靈";
- }
假如你 debug 的時候,進入到 toJson() 方法的內部,就可以觀察到。
foo 的實際類型為 Foo
序列化的時候還好,反序列化的時候就無能為力了。
- Foo<Bar> foo1 = gson.fromJson(json, foo.getClass());
- Bar bar1 = foo1.get();
這段代碼在運行的時候就報錯了。
- Exception in thread "main" java.lang.ClassCastException: class com.google.gson.internal.LinkedTreeMap cannot be cast to class com.itwanger.gson.Bar (com.google.gson.internal.LinkedTreeMap and com.itwanger.gson.Bar are in unnamed module of loader 'app')
- at com.itwanger.gson.Foo.main(Foo.java:36)
默認情況下,泛型的參數類型會被轉成 LinkedTreeMap,這顯然并不是我們預期的 Bar,女朋友對此表示很無奈。
作為 Google 的親兒子,我的血液里流淌著“貴族”二字,我又怎能忍心女朋友無助時的落寞。
于是,我在女朋友的體內植入了另外兩種方法,帶 Type 類型參數的:
- toJson(Object src, Type typeOfSrc);
- <T> T fromJson(String json, Type typeOfT);
這樣的話,你在進行泛型的序列化和反序列化時,就可以指定泛型的參數化類型了。
- Type fooType = new TypeToken<Foo<Bar>>() {}.getType();
- String json = gson.toJson(foo,fooType);
- Foo<Bar> foo1 = gson.fromJson(json, fooType);
- Bar bar1 = foo1.get();
debug 進入 toJson() 方法內部查看的話,就可以看到 foo 的真實類型了。
fromJson() 在反序列化的時候,和此類似。
這樣的話,bar1 就可以通過 foo1.get() 到了。
瞧,我考慮得多周全,女朋友都忍不住夸我了!
05、處理混合類型
你知道的,Java 不建議使用混合類型,也就是下面這種情況。
- List list = new ArrayList();
- list.add("沉默王二");
- list.add(18);
- list.add(new Event("gson", "google"));
Event 的定義如下所示:
- class Event {
- private String name;
- private String source;
- Event(String name, String source) {
- this.name = name;
- this.source = source;
- }
- }
由于 list 沒有指定具體的類型,因此它里面可以存放各種類型的數據。這樣雖然省事,我女朋友在序列化的時候也沒問題,但反序列化的時候就要麻煩多了。
- Gson gson = new Gson();
- String json = gson.toJson(list);
- System.out.println(json);
輸出結果如下所示:
- ["沉默王二",18,{"name":"gson","source":"google"}]
反序列化的時候,就需要花點心思才能拿到 Event 對象。
- JsonParser parser = new JsonParser();
- JsonArray array = parser.parse(json).getAsJsonArray();
- String message = gson.fromJson(array.get(0), String.class);
- int number = gson.fromJson(array.get(1), int.class);
- Event event = gson.fromJson(array.get(2), Event.class);
承認了,JsonParser 是我的前任。希望你不要噴我渣男,真不是我花心,是因為我們性格上有些不太適合。但我們仍然保持著朋友的關系,因為我們誰都沒有錯,只是代碼更加規范了,已經很少有開發者使用混合類型了。
06、個性化定制
考慮到你是一個追求時髦的人,我一直對自己要求很高,力爭能夠滿足你的所有需求。這種高標準的要求,讓我女朋友對我是又愛又恨。
愛的是,我這種追求完美的態度;恨的是,她有時候力不從心,幫不上忙。
使用 toJson() 序列化 Java 對象時,返回的 JSON 字符串中沒有空格,很緊湊。如果你想要打印更漂亮的 JSON 格式,你需要打電話給一個叫 GsonBuilder 的老板,讓他進行一些定制,然后再把復刻版郵寄給你,就像我在使用指南中提到的那樣。
- public class Writer {
- private int age = 18;
- private String name = "沉默王二";
- public static void main(String[] args) {
- Writer writer = new Writer();
- Gson gson = new Gson();
- String json = gson.toJson(writer);
- System.out.println(json);
- Gson gson1 = new GsonBuilder().setPrettyPrinting().create();
- String jsonOutput = gson1.toJson(writer);
- System.out.println(jsonOutput);
- }
- }
來對比一下輸出結果:
- {"age":18,"name":"沉默王二"}
- {
- "age": 18,
- "name": "沉默王二"
- }
通過 setPrettyPrinting() 定制后,輸出的格式更加層次化、立體化,字段與值之間有空格,每個不同的字段之間也會有換行。
之前提到了,默認情況下,我女朋友在序列化的時候會忽略 null 值的字段,如果不想這樣的話,同樣可以打電話給 GsonBuilder。
- public class Writer {
- private int age = 18;
- private String name = null;
- public static void main(String[] args) {
- Writer writer = new Writer();
- Gson gson = new Gson();
- String json = gson.toJson(writer);
- System.out.println(json);
- Gson gson2 = new GsonBuilder().serializeNulls().create();
- String jsonOutput2 = gson2.toJson(writer);
- System.out.println(jsonOutput2);
- }
- }
來對比一下輸出結果:
- {"age":18}
- {"age":18,"name":null}
通過 serializeNulls() 定制后,序列化的時候就不會再忽略 null 值的字段。
也許,你在序列化和反序列化的時候想要篩選一些字段,我也考慮到這種需求了,特意為你準備了幾種方案,你可以根據自己的口味挑選適合你的。
第一種,通過 Java 修飾符。
你之前也看到了,使用 transient 關鍵字修飾的字段將不會參與序列化和反序列化。同樣的,static 關鍵字修飾的字段也不會。如果你想保留這些關鍵字修飾的字段,可以這樣做。
保留單種。
- Gson gson = new GsonBuilder().excludeFieldsWithModifiers(Modifier.TRANSIENT).create();
保留多種。
- Gson gson = new GsonBuilder()
- .excludeFieldsWithModifiers(Modifier.STATIC, Modifier.TRANSIENT, Modifier.VOLATILE)
- .create();
第二種,通過 @Expose 注解。
要使用 @Expose 注解,你需要先這樣做:
- Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
再在需要序列化和反序列化的字段上加上 @Expose 注解,如果沒加的話,該字段將會被忽略。
- @Expose
- private int age = 18;
07、心聲
如果你還想了解更多的話,請來參觀我的 GitHub 主頁:
https://github.com/google/gson
我會向你坦露我的一切,毫不保留的,除了我和女朋友之間的一些秘密,只為能夠幫助到你。
本文轉載自微信公眾號「沉默王二」,可以通過以下二維碼關注。轉載本文請聯系沉默王二公眾號。