Gson——Gson反序列化

轉自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查找到的名稱一樣。因此,我們需要包含如下域名的類:titleisbn-10isbn-13authors。但是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接口實例,並且註冊到GsonBuilderJava文檔)中。下面的例子展示了我們實現的 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對象解析成一個JsonElementJava文檔)類型的Java對象。一個 JsonElement實例可以是下面類型之一:

  • JsonPrimitiveJava Doc):例如一個字符串或整數。
  • JsonObjectJava文檔):JsonElement的集合,以名稱(String類型)爲索引。 與Map<String, JsonElement>Java文檔)相似。
  • JsonArrayJava文檔):JsonElement的集合。注意數組元素可以是任何4中類型,也支持混合類型。
  • JsonNullJava文檔):null值。

上圖顯示了所有 JsonElement的類型。 JsonObject可以被認爲是一個鍵值對的集合,其中值是JsonElement類型。因此,該值可以是其他對象。


上圖以 JsonObject 爲根展示了一個JSON對象層級。特別需要注意,不同於Java,JSON支持不同類型的數組。上圖中, JsonArray 包含JsonObject、JsonArrayJsonPrimitive。請注意,上圖展示的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)時發生了什麼。

  1.   將輸入解析成 JsonElement對象。注意,即使對象的類型是 JsonElement,輸入可以是任何類型。在這個階段,JSON對象字符串被反序列化爲 JsonElement類型的Java對象。這個步驟還確保給定JSON數據的有效性。
  2.   檢索給定對象的反解析器,本例中是 BookDeserializer實例。
  3.   調用 deserialize()方法並提供必需的參數。例子裏,將調用我們的 deserialize()方法。這裏將從給定的 JsonElement對象創建一個 Book類型對象。這是Java內部的轉化。
  4.   返回 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 類?

這裏有幾種選擇。

  1. 我們可以更新 BookDeserializer 並增加反解析作者信息的代碼。這有個限制,它將 Author 的反序列化與 Book綁定了,因此不推薦這個方法。
  2. 我們可以使用默認的Gson實現,該方法在這個例子中工作正常,因爲Java對象(Author類)和JSON對象有同名的字段,可以進行簡單Gson實例文中提到的反序列化。
  3.  或者,我們可以寫一個 AuthorDeserializer 類,該類會處理Author的反序列化。

我們從第二種選擇開始,保證改變最小化,例子儘量簡單。然後我們增加新的反序列化器來展示Gson的靈活性。

JsonDeserializer提供了一個 JsonDeserializationContextJava文檔)實例作爲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變量保存在ThreadLocalJava文檔)中,以隔離多線程之間的狀態。這個類允許多線程使用相同的變量而不會影響到其他線程。

觀察

請注意,儘管這個方法是線程安全的,但它並不滿足特定應用領域的需求,因此不得不使用其他方法。更多緩存實例,請參考如何緩存結果來提高性能緩存使Spring更高效

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對象。有些問題可能比其他問題解決起來更具挑戰性。試着最大程度減少對現有代碼的修改,你的設計將更具靈活性(儘可能不要修改)。











發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章