轉自http://www.importnew.com/16786.html
一個簡單的實例
比方說,我們有如下JSON對象,它包含兩位著名作者的暢銷Java書(Amazon)。
{
'title': 'Java Puzzlers: Traps, Pitfalls, and Corner Cases',
'isbn-10': '032133678X',
'isbn-13': '978-0321336781',
'authors': ['Joshua Bloch', 'Neal Gafter']
}
上面的JSON對象包括4個字段,其中一個是數組。這些字段代表了我們的書籍。使用簡單Gson實例中討論的方法可能產生一個問題。默認情況下,Gson期望Java類中的變量名與JSON查找到的名稱一樣。因此,我們需要包含如下域名的類:title
、isbn-10
、isbn-13
和authors
。但是Java語言規範
(第六章)指出,Java變量名不能包含減號(-
)。我們將在接下來的實例中看到如何使用JsonDeserializer
完全控制JSON的解析。另外我們也可以使用Gson註解實例中提到的註解。註解控制JSON解析的能力稍弱,但是使用簡單便於理解。當然,註解也有它們的限制,不能解決這裏提到的所有問題。
考慮下面簡單的Java對象。
public class Book {
private String[] authors;
private String isbn10;
private String isbn13;
private String title;
// Methods removed for brevity
}
Java對象用來存儲之前JSON對象中的書籍信息。注意,JSON對象有4個字段,每個變量對應一個JSON字段。這兩個對象(Java和JSON)的結構不必一致。Java對象的結構可以與JSON對象不同。
爲了將JSON對象解析成Java對象,我們需要創建自己的 JsonDeserializer
接口實例,並且註冊到GsonBuilder
(Java文檔)中。下面的例子展示了我們實現的 JsonDeserializer
。
import java.lang.reflect.Type;
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
public class BookDeserializer implements JsonDeserializer<Book> {
@Override
public Book deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context)
throws JsonParseException {
//The deserialisation code is missing
final Book book = new Book();
book.setTitle(title);
book.setIsbn10(isbn10);
book.setIsbn13(isbn13);
book.setAuthors(authors);
return book;
}
}
上面的例子是不完整的,我們還需要增加最重要的部分——反序列化。在增加更多代碼之前,我們先了解一下這個類變複雜之前的版本。
JsonDeserializer
接口需要一個類型,該類型是需要我們解析的對象類型。在這個例子裏,我們將JSON對象解析成 Book
類型的Java對象。 deserialize()
方法的返回類型必須與泛型參數一致,爲Book
類型。
Gson將JSON對象解析成一個JsonElement
(Java文檔)類型的Java對象。一個 JsonElement
實例可以是下面類型之一:
JsonPrimitive
(Java Doc):例如一個字符串或整數。JsonObject
(Java文檔):JsonElement
的集合,以名稱(String
類型)爲索引。 與Map<String, JsonElement>
(Java文檔)相似。JsonArray
(Java文檔):JsonElement
的集合。注意數組元素可以是任何4中類型,也支持混合類型。JsonNull
(Java文檔):null
值。
上圖顯示了所有 JsonElement
的類型。 JsonObject
可以被認爲是一個鍵值對的集合,其中值是JsonElement
類型。因此,該值可以是其他對象。
上圖以 JsonObject
爲根展示了一個JSON對象層級。特別需要注意,不同於Java,JSON支持不同類型的數組。上圖中, JsonArray
包含JsonObject、JsonArray
和JsonPrimitive
。請注意,上圖展示的JSON對象層級不反映前面列出的JSON對象。下面前面列出的JSON對象的JSON對象層級。
如果我們反序列化這個JSON對象,首先需要將給定的 JsonElement
轉換爲一個 JsonObject
。
// The variable 'json' is passed as a parameter to the deserialize() method
final JsonObject jsonObject = json.getAsJsonObject();
使用相似的方法,JsonElement
可以轉換成其他任何類型。
JsonObject
中的元素可以使用名稱進行檢索。例如,要從上面列出的JSON對象檢索title
元素,我們可以進行下面操作。
// The variable 'json' is passed as a parameter to the deserialize() method
final JsonObject jsonObject = json.getAsJsonObject();
JsonElement titleElement = jsonObject.get("title")
返回的對象不是一個
String
,而是另一個 JsonElement
。可以調用 getAsString()
方法將 JsonElement
轉換爲 String
,代碼如下:// The variable 'json' is passed as a parameter to the deserialize() method
final JsonObject jsonObject = json.getAsJsonObject();
JsonElement titleElement = jsonObject.get("title")
final String title = jsonTitle.getAsString();
下面的例子展示瞭如何使用定製的反序列化器轉換上面列出的JSON對象。
import java.lang.reflect.Type;
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
public class BookDeserializer implements JsonDeserializer<Book> {
@Override
public Book deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context)
throws JsonParseException {
final JsonObject jsonObject = json.getAsJsonObject();
final JsonElement jsonTitle = jsonObject.get("title");
final String title = jsonTitle.getAsString();
final String isbn10 = jsonObject.get("isbn-10").getAsString();
final String isbn13 = jsonObject.get("isbn-13").getAsString();
final JsonArray jsonAuthorsArray = jsonObject.get("authors").getAsJsonArray();
final String[] authors = new String[jsonAuthorsArray.size()];
for (int i = 0; i < authors.length; i++) {
final JsonElement jsonAuthor = jsonAuthorsArray.get(i);
authors[i] = jsonAuthor.getAsString();
}
final Book book = new Book();
book.setTitle(title);
book.setIsbn10(isbn10);
book.setIsbn13(isbn13);
book.setAuthors(authors);
return book;
}
}
上例中,我們檢索JSON元素和它的4個字段,並返回了一個 Book
實例。
在可以使用新的反序列化器之前,必須指定Gson使用我們的反序列化器來解析 Book
類型的對象,代碼如下:
import java.io.InputStreamReader;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class Main {
public static void main(String[] args) throws Exception {
// Configure Gson
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Book.class, new BookDeserializer());
Gson gson = gsonBuilder.create();
// The JSON data
try(Reader reader = new InputStreamReader(Main.class.getResourceAsStream("/part1/sample.json"), "UTF-8")){
// Parse JSON to Java
Book book = gson.fromJson(reader, Book.class);
System.out.println(book);
}
上例中,我們通過 GsonBuilder
創建了一個 Gson
實例。使用 registerTypeAdapter()
方法向Gson註冊了我們的反序列化器,並指定反序列化 Book
類型對象時使用我們定義的反序列化器。當請求Gson反序列化一個 Book
類對象時,Gson將使用我們定義的反序列化器。下面的步驟描述了我們調用 gson.fromJson(data, Book.class)
時發生了什麼。
- 將輸入解析成
JsonElement
對象。注意,即使對象的類型是JsonElement
,輸入可以是任何類型。在這個階段,JSON對象字符串被反序列化爲JsonElement
類型的Java對象。這個步驟還確保給定JSON數據的有效性。 - 檢索給定對象的反解析器,本例中是
BookDeserializer
實例。 - 調用
deserialize()
方法並提供必需的參數。例子裏,將調用我們的deserialize()
方法。這裏,將從給定的JsonElement
對象創建一個Book
類型對象。這是Java內部的轉化。 - 返回
deserialize()
方法的返回值到調用者fromJson()
方法。這一步像一個鏈條,Gson從我們的反序列化器接收一個對象並返回給它的調用者。
執行上面的例子可能得到下面的輸出:
Java Puzzlers: Traps, Pitfalls, and Corner Cases
[ISBN-10: 032133678X] [ISBN-13: 978-0321336781]
Written by:
>> Joshua Bloch
>> Neal Gafter
至此,我們結束了簡單的例子。本例是後續更復雜例子的引子。在下一個例子裏,我們將討論當前對象的一個增強版本,該版本的作者對象不僅是一個簡單的字符串,而是一個對象。
嵌套對象
本例中,我們將描述如何反序列化嵌套對象,也就是對象包含對象。這裏,我們將介紹一個新的實體,作者。一本書,除了有標題和ISBN號,還可以有多個作者。換句話說,每個作者可以寫多本書。爲了增加新的實體,本例中的JSON對象做了修改,與前例不同:
{
'title': 'Java Puzzlers: Traps, Pitfalls, and Corner Cases',
'isbn': '032133678X',
'authors':[
{
'id': 1,
'name': 'Joshua Bloch'
},
{
'id': 2,
'name': 'Neal Gafter'
}
]
}
稍微調整了JSON對象的結構並用JSON對象的作者數組代替了之前的原型,如下圖:
我們還是以一本書爲例,只是這次我們有了更復雜和更詳細的JSON對象。除了 name字段
,作者對象還有一個 id字段
。爲這個模型增加了新類稱爲 Author
, Book
類用它保存作者信息。這立即導致如下問題。
如何反序列化新的 Author
類?
這裏有幾種選擇。
- 我們可以更新
BookDeserializer
並增加反解析作者信息的代碼。這有個限制,它將Author
的反序列化與Book
綁定了,因此不推薦這個方法。 - 我們可以使用默認的Gson實現,該方法在這個例子中工作正常,因爲Java對象(
Author
類)和JSON對象有同名的字段,可以進行簡單Gson實例文中提到的反序列化。 - 或者,我們可以寫一個
AuthorDeserializer
類,該類會處理Author的反序列化。
我們從第二種選擇開始,保證改變最小化,例子儘量簡單。然後,我們增加新的反序列化器來展示Gson的靈活性。
JsonDeserializer
提供了一個 JsonDeserializationContext
(Java文檔)實例作爲deserialize()
方法的第三個參數。我們還沒有用過這個參數。我們可以將對象的反序列化委託給指定的 JsonDeserializationContext
實例。它將反序列化給定的 JsonElement
並返回一個指定類型的實例,代碼如下。
1
|
Author author = context.deserialize(jsonElement, Author. class ); |
上例將 Author
類的反序列化委託給 context
變量。反過來,它試圖搜索已註冊的可以反序列化 Author
類的 JsonDeserialize
實例,如果未發現註冊的實例,它將使用簡單Gson實例中提到的默認機制。
我們的例子使用了一個 Author
數組,因此我們需要使用正確的類型,例子如下:
import java.lang.reflect.Type;
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
public class BookDeserializer implements JsonDeserializer<Book> {
@Override
public Book deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context)
throws JsonParseException {
final JsonObject jsonObject = json.getAsJsonObject();
final String title = jsonObject.get("title").getAsString();
final String isbn10 = jsonObject.get("isbn-10").getAsString();
final String isbn13 = jsonObject.get("isbn-13").getAsString();
// Delegate the deserialization to the context
Author[] authors = context.deserialize(jsonObject.get("authors"), Author[].class);
final Book book = new Book();
book.setTitle(title);
book.setIsbn10(isbn10);
book.setIsbn13(isbn13);
book.setAuthors(authors);
return book;
}
}
從 JsonPrimitive
轉換成 JsonObject
是十分簡單直接的,就像上面例子中看到的那樣。
與BookDeserialiser類似
,我們可以編寫 ArthurDeserialiser
類並使用處理書籍相似的方式反序列化作者。
import java.lang.reflect.Type;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
public class AuthorDeserializer implements JsonDeserializer {
@Override
public Author deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context)
throws JsonParseException {
final JsonObject jsonObject = json.getAsJsonObject();
final Author author = new Author();
author.setId(jsonObject.get("id").getAsInt());
author.setName(jsonObject.get("name").getAsString());
return author;
}
}
爲了使用 ArthurDeserialiser
,我們需要向 GsonBuilder
註冊,
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
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 BookDeserializer());
gsonBuilder.registerTypeAdapter(Author.class, new AuthorDeserializer());
final Gson gson = gsonBuilder.create();
// Read the JSON data
try (Reader reader = new InputStreamReader(Main.class.getResourceAsStream("/part2/sample.json"), "UTF-8")) {
// Parse JSON to Java
final Book book = gson.fromJson(reader, Book.class);
System.out.println(book);
}
}
}
沒有必要改變 BookDeserialiser
類,因爲作者的反序列化委託給了 context
變量。這是另一個使用 context
反序列化其他對象或嵌套對象的優點。運行上面的代碼將產生下面的輸出。
Java Puzzlers: Traps, Pitfalls, and Corner Cases [032133678X]
Written by:
>> [1] Joshua Bloch
>> [2] Neal Gafter
至此我們結束了嵌套對象的介紹。下一章節我們將看到如何引用JSON對象樹中其他位置的JSON對象。
對象引用
考慮下面的JSON。
{
'authors': [
{
'id': 1,
'name': 'Joshua Bloch'
},
{
'id': 2,
'name': 'Neal Gafter'
}
],
'books': [
{
'title': 'Java Puzzlers: Traps, Pitfalls, and Corner Cases',
'isbn': '032133678X',
'authors':[1, 2]
},
{
'title': '<span class="wp_keywordlink"><a href="http://www.amazon.com/gp/product/B000WJOUPA/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=B000WJOUPA&linkCode=as2&tag=job0ae-20" title="Effective Java" rel="nofollow" target="_blank" class="external">Effective Java</a></span> (2nd Edition)',
'isbn': '0321356683',
'authors':[1]
}
]
}
上面的JSON對象由兩個作者和兩本書組成。書有一個id到作者的引用,本例中書的 authors
字段只包含作者的id。這是一個很常見的場景,通過這個方法將減少JSON對象的大小,因爲重複對象通過他們的id進行引用。下圖展示了JSON新對象的層級。
這類似於關係型數據庫(維基),其中book對象有一個到作者表的外鍵(維基)。新的JSON對象引入了需要解決的新挑戰。當反序列化書籍對象時,我們需要保持作者對象,並從JSON對象層級的其他分支反序列化它們。書籍對象只有作者對象的id。作者對象的其他信息不在當前上下文中,需要從其他地方進行解析。
這裏有多種方式來解決這個問題,下面列出了一些。
1. 一種方法是分兩個階段處理。首先將JSON對象解析成Java對象,這一步反序列化JSON中的書和作者對象。書籍類包含作者的id數組而不是作者的數組。接着,第二階段,我們關聯對象,將作者對象關聯到書籍對象。下圖顯示了反解析流程。
兩階段處理過程
這個方法需要很多類,但是提供了很大的靈活性並且更好地分離了關注點。我們需要創建一組代表JSON對象的簡單Java類,接着創建另一組滿足我們需求(模型)的類。這個例子裏,我們有一個 Book
和一個Author
類,總共兩個。使用這個方法,我們最終將有4個類,兩個代表書籍類,另兩個代表作者類。本例使用這個方法似乎是可行的,但是當有數十個類時,就會變得十分複雜。
2. 另一種方法是提供包含所有作者的BookDeserialiser
類,接着使用反序列化器從公用對象檢索所有作者。這種方法消除了中間狀態,因爲JSON對象沒有經過中間階段就被反序列化成適當的Java對象。
反序列化器共享對象
儘管這個方法聽上去有吸引力,但它要求 BookDeserialiser
和 AuthorDeserialiser
共享一個對象。此外,噹噹檢索作者時, BookDeserialiser
不得不引用這個共享對象來代替之前使用的JsonDeserializationContext
類。這個方法需要修改幾個地方,反序列化器和main()
函數都需要修改。
3. AuthorDeserialiser
可以換成反序列化的作者,並在下次指定ID的請求時返回它們。這個方法十分有吸引力,因爲它充分利用了 JsonDeserializationContext
,並且使得關係透明。不幸的是,它增加了複雜性,AuthorDeserialiser
需要處理緩存。按照這個說法,這種方法需要最少的修改,只有 AuthorDeserialiser
需要修改。
AuthorDeserialiser
反序列化器使用緩存對象
如上如所示,只有 AuthorDeserialiser
類訪問緩存對象。系統裏的其他部分不知道這一點。
所有方法都是可行的,而且每種都有他們的優缺點。我們將使用第三種方法,因爲它對工程的影響最小。
觀察
Data
類中處理關聯邏輯。但與第三種方法相比,這需要很多修改。這就是使用第三種方法的原因。始終考慮改變,並儘量減少所需的工作。上面所示的JSON對象包含兩個數組。我們需要新的Java類來反射這個JSON對象。
public class Data {
private Author[] authors;
private Book[] books;
// Methods removed for brevity
}
字段順序決定了兩個集合反序列化的順序。在我們的例子中沒有問題,在後面我們可以看到,書籍集合可以在作者集合之前被反序列化。
AuthorDeserialiser
類需要做出修改,它會緩存反序列化出的作者對象。
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
public class AuthorDeserializer implements JsonDeserializer<Author> {
private final ThreadLocal<Map<Integer, Author>> cache = new ThreadLocal<Map<Integer, Author>>() {
@Override
protected Map<Integer, Author> initialValue() {
return new HashMap<>();
}
};
@Override
public Author deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context)
throws JsonParseException {
// Only the ID is available
if (json.isJsonPrimitive()) {
final JsonPrimitive primitive = json.getAsJsonPrimitive();
return getOrCreate(primitive.getAsInt());
}
// The whole object is available
if (json.isJsonObject()) {
final JsonObject jsonObject = json.getAsJsonObject();
final Author author = getOrCreate(jsonObject.get("id").getAsInt());
author.setName(jsonObject.get("name").getAsString());
return author;
}
throw new JsonParseException("Unexpected JSON type: " + json.getClass().getSimpleName());
}
private Author getOrCreate(final int id) {
Author author = cache.get().get(id);
if (author == null) {
author = new Author();
author.setId(id);
cache.get().put(id, author);
}
return author;
}
}
我們在這個類中做了一些修改。讓我們一個個地說明。
1.作者保存在下面的對象中
private final ThreadLocal<Map<Integer, Author>> cache = new ThreadLocal<Map<Integer, Author>>() {
@Override
protected Map<Integer, Author> initialValue() {
return new HashMap<>();
}
};
它使用 Map<String, Object>
提供緩存機制。map變量保存在ThreadLocal
(Java文檔)中,以隔離多線程之間的狀態。這個類允許多線程使用相同的變量而不會影響到其他線程。
觀察
2.總是通過下面方法得到作者。
private Author getOrCreate(final int id) {
Author author = cache.get().get(id);
if (author == null) {
author = new Author();
cache.get().put(id, author);
}
return author;
}
這個方法首先從緩存中獲取作者實例,如果沒有找到給定id的作者,那麼將創建一個並加入到緩存中。
這個方法允許我們只用id就能創建作者,之後當它們可用時公佈它們的內容。這就是爲什麼反序列化屬性不影響輸出。我們可以先反序列化書籍類,再反序列化作者類。在這個例子中,首先使用id創建作者,然後給他們增加名字。
3.對 deserialize()
函數進行修改來處理新的需求。因爲修改了很多地方,我們將拆分這個方法,並逐個講解。反序列化器可以接收JsonPrimitive
變量或者JsonObject
變量。
當BookDeserialiser
執行下面的代碼時,傳遞給 AuthorDeserialiser
的 JsonElement
變量將會是一個JsonPrimitive
實例。
上下文收到 BookDeserialiser
委託反序列化 Author
數組的操作,並返回一個整型數組。對於每個整數,上下文作爲一個 JsonPrimitive
對象傳遞給 AuthorDeserialiser
的 deserialize()
方法。
另一方面,當作者被反序列化後,我們將收到一個包含作者和他或她詳細信息的 JsonObject
實例。因此,在我們轉換給定的JsonElement
對象前,需要校驗他是否是正確的類型。
// Only the ID is available
if (json.isJsonPrimitive()) {
final JsonPrimitive primitive = json.getAsJsonPrimitive();
final Author author = getOrCreate(primitive.getAsInt());
return author;
}
上例所示,只有id是有效的。 JsonElement
轉換爲JsonPrimitive
接着又轉換爲 int
.。
JsonElement
可以是JsonObject
類型,如下所示。
// The whole object is available
if (json.isJsonObject()) {
final JsonObject jsonObject = json.getAsJsonObject();
final Author author = getOrCreate(jsonObject.get("id").getAsInt());
author.setName(jsonObject.get("name").getAsString());
return author;
}
這個例子中,在返回author前,向 getOrCreate()
方法返回的 Author
實例中增加name字段。
最後,如果給定的JsonElement
實例既不是 JsonPrimitive
也不是 JsonObject
,將拋出一個異常說明不支持指定類型。
throw new JsonParseException("Unexpected JSON type: " + json.getClass().getSimpleName());
以上代碼塊列出了所有需要的修改BookDeserialiser
類和 main()
方法不需要任何修改。執行main()
將得到如下輸出。
Output missing...
這個例子總結了我們關於Gson反序列化器的文章。使用定製反序列化器不困難,可以使我們毫不費力的處理不同的JSON示例。需要注意的是,在Java業務對象不需要與解析的JSON對象對應。此外,我們可以使用新的JSON表示現有的Java對象。有些問題可能比其他問題解決起來更具挑戰性。試着最大程度減少對現有代碼的修改,你的設計將更具靈活性(儘可能不要修改)。