Java Json API:Gson序列化

ImportNew發表於2015-09-09

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的型別

完全理解Gson(2):Gson序列化

上面這張圖片展示了 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 字串。

相關文章