Java Json API:Gson序列化
通過呼叫 Gson API 可以把 Java 物件轉換為 JSON 格式的字串(專案主頁)。在這篇文章中,我們將會講到如何通過 Gson 預設實現和自定義實現方式,將 Java 物件轉換為 JSON 字串。
對於那些不熟悉 Gson 的讀者,建議在讀本篇文章之前讀一下這兩篇文章:簡單 Gson 例項和 Gson 反序列化例項。另外,這篇文章的講述方式和Gson反序列化例項一樣,並且使用了相同的例子。
注意
請注意,在文章中我們將互換格式化或序列化的術語。
下面列出的所有程式碼都可以在這裡找到: http://java-creed-examples.googlecode.com/svn/gson/Gson Serialiser Example/ 。大多數例子不會包含完整的程式碼,可能會忽略和要討論的例子不相關的片段。讀者可以從上面的連結下載檢視完整的程式碼。
簡單的例子
考慮下面這個 Java 物件。
package com.javacreed.examples.gson.part1; public class Book { private String[] authors; private String isbn10; private String isbn13; private String title; // Methods removed for brevity }
這個簡單的 Java 類封裝了一本書的屬性。假如我們需要將其序列化為下面這個 JSON 物件。
{ "title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases", "isbn-10": "032133678X", "isbn-13": "978-0321336781", "authors": [ "Joshua Bloch", "Neal Gafter" ] }
Gson 不需要任何特殊配置就可以序列化 Book 類。Gson 使用 Java 欄位名稱作為 JSON 欄位的名稱,並賦予對應的值。如果仔細地看一下上面的那個 JSON 示例會發現, ISBN 欄位包含一個減號:isbn-10 和 isbn-13。不幸的是,使用預設配置不能將這些欄位包含進來。解決問題的辦法之一就是使用註解,就像在這篇文章中描述的那樣:Gson 註解示例。使用註解可以自定義 JSON 欄位的名稱,Gson將會以註解為準進行序列化。另一個方法就是使用 JsonSerialiser
(Java Doc),如下所示:
package com.javacreed.examples.gson.part1; import java.lang.reflect.Type; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; public class BookSerialiser implements JsonSerializer { @Override public JsonElement serialize(final Book book, final Type typeOfSrc, final JsonSerializationContext context) { final JsonObject jsonObject = new JsonObject(); //The serialisation code is missing return jsonObject; } }
上面的例子還缺失了重要部分,需要通過補充序列化程式碼來完善。在新增更多程式碼使其變得複雜之前,我們先來理解下這個類。
JsonSerializer 介面要求型別是將要進行序列化的物件型別。在這個例子中,我們要序列化的 Java 物件是 Book。serialize()方法的返回型別必須是一個 JsonElement (Java 文件)型別的例項。詳見這篇文章:Gson 反序列化例項,下面列出了JsonElement 四種具體實現型別:
JsonPrimitive
(Java Doc) —— 例如一個字串或整型JsonObject
(Java Doc) —— 一個以 JsonElement 名字(型別為 String)作為索引的集合。類似於 Map<String,JsonElement>集合(Java Doc)JsonArray
(Java Doc)—— JsonElement 的集合。注意陣列的元素可以是四種型別中的任意一種,或者混合型別都支援。JsonNull
(Java Doc) —— 值為null
JsonElement的型別
上面這張圖片展示了 JsonElement 的所有型別。可以把 JsonObject 看作值為 JsonElement 的鍵值對集合。因此,這些值可以是另外四種物件。
下面是序列化的完整例項:
package com.javacreed.examples.gson.part1; import java.lang.reflect.Type; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; public class BookSerialiser implements JsonSerializer { @Override public JsonElement serialize(final Book book, final Type typeOfSrc, final JsonSerializationContext context) { final JsonObject jsonObject = new JsonObject(); jsonObject.addProperty("title", book.getTitle()); jsonObject.addProperty("isbn-10", book.getIsbn10()); jsonObject.addProperty("isbn-13", book.getIsbn13()); final JsonArray jsonAuthorsArray = new JsonArray(); for (final String author : book.getAuthors()) { final JsonPrimitive jsonAuthor = new JsonPrimitive(author); jsonAuthorsArray.add(jsonAuthor); } jsonObject.add("authors", jsonAuthorsArray); return jsonObject; } }
我們在這裡新增了一些程式碼。在理解整個圖片的含義前,我們先把它拆成一個個小的部分,先來解釋下每部分的含義。
如果要序列化這個 Java 物件,首先需要建立一個 JsonElement 例項。例子中是返回了一個 JsonObject 例項來代表 Book 物件,如下所示:
final JsonObject jsonObject = new JsonObject();
該物件使用我們設定的欄位名稱進行填充,如下:
// The variable 'book' is passed as a parameter to the serialize() method final JsonObject jsonObject = new JsonObject(); jsonObject.addProperty("title", book.getTitle()); jsonObject.addProperty("isbn-10", book.getIsbn10()); jsonObject.addProperty("isbn-13", book.getIsbn13());
使用 addProperty() 方法 (Java Doc)可以新增任何 Java 原始型別以及String和Number。注意此處的 name 必須是唯一的,否則會被前一個覆蓋掉。可以將其看做是一個將欄位名作為值索引的 Map。
更復雜的物件,比如 Java 物件或陣列就不能使用上面的方法來新增了。JsonObject 有另外一個 add() 方法,可以用來作為替代,如下所示:
// The variable 'book' is passed as a parameter to the serialize() method jsonObject.addProperty("title", book.getTitle()); jsonObject.addProperty("isbn-10", book.getIsbn10()); jsonObject.addProperty("isbn-13", book.getIsbn13()); final JsonArray jsonAuthorsArray = new JsonArray(); for (final String author : book.getAuthors()) { final JsonPrimitive jsonAuthor = new JsonPrimitive(author); jsonAuthorsArray.add(jsonAuthor); } jsonObject.add("authors", jsonAuthorsArray);
首先建立一個 JsonArray 物件,然後將所有 authors 新增進去。和 Java 不同的是,初始化 JsonArray 時不需要指定陣列的大小。事實上,拋開這個類的名字不看,可以將 JsonArray 類更多地看做是一個 list 而非 array。最後將 jsonAuthorsArray
新增到 jsonObject 中。此處也可以在給 jsonAuthorsArray
新增元素之前,將其新增到 jsonObject 中。
在呼叫該序列化方法之前,我們需要將其註冊到 Gson 中:
package com.javacreed.examples.gson.part1; import java.io.IOException; import com.google.gson.Gson; import com.google.gson.GsonBuilder; public class Main { public static void main(final String[] args) throws IOException { // Configure GSON final GsonBuilder gsonBuilder = new GsonBuilder(); gsonBuilder.registerTypeAdapter(Book.class, new BookSerialiser()); gsonBuilder.setPrettyPrinting(); final Gson gson = gsonBuilder.create(); final Book javaPuzzlers = new Book(); javaPuzzlers.setTitle("Java Puzzlers: Traps, Pitfalls, and Corner Cases"); javaPuzzlers.setIsbn10("032133678X"); javaPuzzlers.setIsbn13("978-0321336781"); javaPuzzlers.setAuthors(new String[] { "Joshua Bloch", "Neal Gafter" }); // Format to JSON final String json = gson.toJson(javaPuzzlers); System.out.println(json); } }
通過註冊我們自己實現的序列化器,告訴 Gson 無論什麼時候序列化 Book 型別的物件都使用該序列化器進行序列化。
在上面例子中,通過呼叫 set prettying printing 方法還告訴了 Gson 對生成的 JSON 物件進行格式化,如下所示:
gsonBuilder.setPrettyPrinting();
雖然這對於除錯和教程非常有用,但請不要在生產環境中這樣用,因為可能會因此產生更大的 JSON 物件(文字的大小)。除此之外,由於 Gson 必須要格式化 JSON 物件,即對其進行相應的縮排,pretty printing 會有一些效能方面的消耗。
執行上面的程式碼可以得到預期的 JSON 物件。對我們的第一個例子做個總結,即怎樣自定義 Gson 序列化器將 Java 物件序列化為 JSON 物件。下一章將會講到怎樣使用 Gson 序列化巢狀物件。
巢狀物件
接下來的例子將會描述怎麼序列化巢狀物件。所謂巢狀物件是指在其它物件內部的物件。在此我們將會引入一個新的實體:author。形成了這樣一個包含 title 和 ISBN 連同 author 列表的 book。在這個例子中將會得到一個包含新實體的 JSON 物件,與前面的JSON物件不同,就像下面那樣:
{ "title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases", "isbn": "032133678X", "authors": [ { "id": 1, "name": "Joshua Bloch" }, { "id": 2, "name": "Neal Gafter" } ] }
注意,前一個例子中 authors 只是一個簡單的字元陣列:
"authors": [ "Joshua Bloch", "Neal Gafter" ]
這個例子中的 authors 是一個 JSON 物件,而不僅僅只是一個基本型別。
{ "id": 1, "name": "Joshua Bloch" }
author 的 JSON物件有一個 id 和一個 name欄位。下面是 Author 類。
package com.javacreed.examples.gson.part2; public class Author { private int id; private String name; // Methods removed for brevity }
這個類非常簡單,由兩個欄位組成,並且都是原始型別。Book 類被修改為使用 Author 類,如下所示:
package com.javacreed.examples.gson.part2; public class Book { private Author[] authors; private String isbn; private String title; // Methods removed for brevity }
author 欄位從一個 integer 陣列變成了一個 Author 陣列。因此必須修改下 BookSerialiser 類來相容這一改變,如下:
package com.javacreed.examples.gson.part2; import java.lang.reflect.Type; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; public class BookSerialiser implements JsonSerializer<Book> { @Override public JsonElement serialize(final Book book, final Type typeOfSrc, final JsonSerializationContext context) { final JsonObject jsonObject = new JsonObject(); jsonObject.addProperty("title", book.getTitle()); jsonObject.addProperty("isbn", book.getIsbn()); final JsonElement jsonAuthros = context.serialize(book.getAuthors()); jsonObject.add("authors", jsonAuthros); return jsonObject; } }
authors 的序列化由 context(作為 serialize() 方法的一個引數被傳進來,是 JsonSerializationContext 的例項) 來完成。context 將會序列化給出的物件,並返回一個 JsonElement。同時 context 也會嘗試找到一個可以序列化當前物件的序列化器。如果沒有找到,其將會使用預設的序列化器。目前,我們還不會為 Author 類實現一個序列化器,仍然會使用預設實現作為替代。
package com.javacreed.examples.gson.part2; import java.io.IOException; import com.google.gson.Gson; import com.google.gson.GsonBuilder; public class Main { public static void main(final String[] args) throws IOException { // Configure GSON final GsonBuilder gsonBuilder = new GsonBuilder(); gsonBuilder.registerTypeAdapter(Book.class, new BookSerialiser()); gsonBuilder.setPrettyPrinting(); final Gson gson = gsonBuilder.create(); final Author joshuaBloch = new Author(); joshuaBloch.setId(1); joshuaBloch.setName("Joshua Bloch"); final Author nealGafter = new Author(); nealGafter.setId(2); nealGafter.setName("Neal Gafter"); final Book javaPuzzlers = new Book(); javaPuzzlers.setTitle("Java Puzzlers: Traps, Pitfalls, and Corner Cases"); javaPuzzlers.setIsbn("032133678X"); javaPuzzlers.setAuthors(new Author[] { joshuaBloch, nealGafter }); final String json = gson.toJson(javaPuzzlers); System.out.println(json); } }
上面的例子建立並配置 Gson 使用自定義的 BookSerialiser 進行序列化。我們建立了兩個 author 物件和一個 book 物件,並序列化該 book 物件。這樣將會得到在章節開始時展示的 JSON 物件。
完整起見,下面是一個 Author 類的序列化器:
package com.javacreed.examples.gson.part2; import java.lang.reflect.Type; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; public class AuthorSerialiser implements JsonSerializer<Author> { @Override public JsonElement serialize(final Author author, final Type typeOfSrc, final JsonSerializationContext context) { final JsonObject jsonObject = new JsonObject(); jsonObject.addProperty("id", author.getId()); jsonObject.addProperty("name", author.getName()); return jsonObject; } }
上面的序列化器並沒有引入什麼新的特性,因此不需要再多做解釋。如果想要使用這個新的序列器,需要將其註冊到 GsonBuilder (Java Doc) 中。
本章對巢狀物件的序列化做了總結。可以將巢狀物件的序列化交給 context 處理,其在序列化時會順帶嘗試找到一個合適的序列化器,並返回相應的 JsonElement。下一章也是最後一章將會講如何處理物件引用的序列化。
物件引用
一個物件對其它物件的引用叫做物件引用,book 和 author 類之間的關係就是這樣。同一個 author 可以有多本 book。例如 author Joshua Bloch(Author at Amazon)不止一本 book。在使用序列化器描述之前,我們將會清除重複的 author。
考慮下面這個例子:
package com.javacreed.examples.gson.part3; import java.io.IOException; import com.google.gson.Gson; import com.google.gson.GsonBuilder; public class Example1 { public static void main(final String[] args) throws IOException { // Configure GSON final GsonBuilder gsonBuilder = new GsonBuilder(); gsonBuilder.setPrettyPrinting(); final Gson gson = gsonBuilder.create(); final Author joshuaBloch = new Author(); joshuaBloch.setId(1); joshuaBloch.setName("Joshua Bloch"); final Author nealGafter = new Author(); nealGafter.setId(2); nealGafter.setName("Neal Gafter"); final Book javaPuzzlers = new Book(); javaPuzzlers.setTitle("Java Puzzlers: Traps, Pitfalls, and Corner Cases"); javaPuzzlers.setIsbn("032133678X"); javaPuzzlers.setAuthors(new Author[] { joshuaBloch, nealGafter }); final Book effectiveJava = new Book(); effectiveJava.setTitle("Effective Java (2nd Edition)"); effectiveJava.setIsbn("0321356683"); effectiveJava.setAuthors(new Author[] { joshuaBloch }); final Book[] books = new Book[] { javaPuzzlers, effectiveJava }; final String json = gson.toJson(books); System.out.println(json); } }
兩個作者和兩本書,其中一個作者在兩本書中都有。注意,有兩個 author 物件而不是三個,代表 Joshua Bloch 的例項被兩個 book 物件共享。最後注意,我們故意沒有使用任何自定義的序列化器。下面是執行上面程式碼的結果。
[ { "authors": [ { "id": 1, "name": "Joshua Bloch" }, { "id": 2, "name": "Neal Gafter" } ], "isbn": "032133678X", "title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases" }, { "authors": [ { "id": 1, "name": "Joshua Bloch" } ], "isbn": "0321356683", "title": "Effective Java (2nd Edition)" } ]
得到了兩個 book 型別的 JSON 和三個 author 型別的 JSON。而對於author:Joshua Bloch 是重複的。
{ "id": 1, "name": "Joshua Bloch" }
這會使得 JSON 物件明顯變大,尤其是對更復雜的物件。理想情況下,book 的 JSON 物件應該只包含 author 的 id 而不是整個物件。這類似於關係型資料庫中 book 表有一個外來鍵關聯到 author 表。
Book 類將會被修改為包含一個只返回 author 的 id 的方法,如下:
package com.javacreed.examples.gson.part3; public class Book { private Author[] authors; private String isbn; private String title; public Author[] getAuthors() { return authors; } public int[] getAuthorsIds() { final int[] ids = new int[authors.length]; for (int i = 0; i < ids.length; i++) { ids[i] = authors[i].getId(); } return ids; }// Other methods removed for brevity }
當序列化 book 時,使用 author 的 id 代替整個 author 物件,如下:
package com.javacreed.examples.gson.part3; import java.lang.reflect.Type; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; public class BookSerialiser implements JsonSerializer { @Override public JsonElement serialize(final Book book, final Type typeOfSrc, final JsonSerializationContext context) { final JsonObject jsonObject = new JsonObject(); jsonObject.addProperty("title", book.getTitle()); jsonObject.addProperty("isbn", book.getIsbn()); final JsonElement jsonAuthros = context.serialize(book.getAuthorsIds()); jsonObject.add("authors", jsonAuthros); return jsonObject; } }
下面是使用這個序列化器得到的結果:
[ { "title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases", "isbn": "032133678X", "authors": [ 1, 2 ] }, { "title": "Effective Java (2nd Edition)", "isbn": "0321356683", "authors": [ 1 ] } ]
現在我們使用 author 的 id 代替了整個 author。這一做法使得其比前一個有更小的空間佔用。對於更大的物件將產生巨大的影響。
這個例子是整篇文章關於序列化的總結。從中我們學會了怎麼使用預設或自定義的序列化選項,將 Java 物件序列化為 JSON 字串。
相關文章
- Java Json API:Gson使用簡單入門JavaJSONAPI
- Eclipse安裝GSON,使用GSON轉換Java Object到JSONEclipseJavaObjectJSON
- java基礎知識-序列化/反序列化-gson基礎知識Java
- 如何使用JSON和GSONJSON
- Json解析之Gson庫JSON
- Java物件的序列化與反序列化-Json篇Java物件JSON
- android使用Gson來解析jsonAndroidJSON
- 生成JSON資料--Gson(谷歌)方法JSON谷歌
- kotlin gson反序列化預設值失效Kotlin
- Jackson Gson Json.simple 比較JSON
- JSON 物件序列化、反序列化JSON物件
- JSON-B:簡化 JSON 序列化和反序列化JSON
- json與gson工具轉換案例及區別JSON
- json解析效能比較(gson與jackson)JSON
- Flutter中JSON序列化與反序列化FlutterJSON
- C#序列化和反序列化(json)C#JSON
- C# 序列化與反序列化jsonC#JSON
- Kotlin Json 序列化KotlinJSON
- Java 下的 JSON庫效能比較:JSON.simple vs. GSON vs. Jackson vs. JSONPJavaJSON
- JSON解析器之Gson、FastJson、JacksonJSONAST
- FastJson、Jackson、Gson進行Java物件轉換Json的細節處理ASTJSONJava物件
- 序列化: 一個老傢伙的鹹魚翻身(Java物件、XML、JSON、反序列化)Java物件XMLJSON
- C# Json 序列化與反序列化一C#JSON
- C# Json 序列化與反序列化二C#JSON
- 序列化和反序列化pickle和json 模組JSON
- Json和Map互轉,三個包(org.json/net.sf.json/com.google.gson)JSONGo
- JavaScript物件序列化為JSONJavaScript物件JSON
- C# Json反序列化C#JSON
- Gson – 比較兩個順序無關的json字串JSON字串
- Android總結之json解析(FastJson Gson 對比)AndroidJSONAST
- Newtonsoft.Json序列化JSON字串問題JSON字串
- python 學習 -- json的序列化和反序列化PythonJSON
- json - 使用jackson進行序列化/反序列化JSON
- jackson進行json序列化和反序列化JSON
- json無法序列化問題JSON
- Swift 中的 JSON 反序列化SwiftJSON
- 客戶端序列化JSON物件客戶端JSON物件
- Json序列化字串異常情況JSON字串