1. Gson 入門
我們已經接觸過 Jackson,並了解為何它被視為在 Java 中處理 JSON 的事實標準。不過還有另一個在 Android 世界尤其受歡迎的函式庫——Gson。Gson 由 Google 開發,作為將 Java 物件與 JSON 之間進行序列化/反序列化的輕量簡單解決方案。它的上手門檻很低:要開始使用幾乎不需設定——多數需求都能「開箱即用」解決。
Gson 的另一個優點是輕量。函式庫體積小,且不會引入一堆相依,因此常見於對應用程式大小很敏感的場景,例如行動裝置上。Gson 幾乎已成為 Android 專案的事實標準——在那裡,精簡與簡單至關重要。
順帶一提,Gson 這個名稱是 Google JSON 的縮寫。社群裡有時會幽默地解讀為 Genius’ Son(意為「天才之子」),當然這不是官方說法,只是雙關語的玩笑。
在專案中加入 Gson
若你使用 Maven 或 Gradle,只要加入依賴即可(版本可能不同):
Maven:
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
Gradle:
implementation 'com.google.code.gson:gson:2.10.1'
我們暫時還沒學到專案的建置,因此一開始可以直接從 Gson 官方頁面 下載 jar 檔並加入專案。
2. 基本操作:序列化與反序列化
讓我們以一個簡單的類別為例,看看如何用 Gson 進行物件的序列化與反序列化。
範例:User 類別
// 範例用的類別
public class User {
private String name;
private int age;
private boolean active;
// 建構子
public User(String name, int age, boolean active) {
this.name = name;
this.age = age;
this.active = active;
}
// Getter 與 Setter(Gson 會在需要時使用)
public String getName() { return name; }
public int getAge() { return age; }
public boolean isActive() { return active; }
}
序列化:物件 → JSON
import com.google.gson.Gson;
public class GsonExample {
public static void main(String[] args) {
User user = new User("Alice", 25, true);
Gson gson = new Gson();
String json = gson.toJson(user);
System.out.println(json);
// {"name":"Alice","age":25,"active":true}
}
}
請注意:欄位會以其在類別中的名稱進行序列化!
反序列化:JSON → 物件
public class GsonExample {
public static void main(String[] args) {
String json = "{\"name\":\"Bob\",\"age\":30,\"active\":false}";
Gson gson = new Gson();
User user = gson.fromJson(json, User.class);
System.out.println(user.getName()); // Bob
System.out.println(user.getAge()); // 30
System.out.println(user.isActive());// false
}
}
處理物件清單
Gson 在集合處理上比 Jackson 稍微麻煩,但都有解法。
import java.util.List;
import java.util.Arrays;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
public class GsonListExample {
public static void main(String[] args) {
List<User> users = Arrays.asList(
new User("Alice", 25, true),
new User("Bob", 30, false)
);
Gson gson = new Gson();
String json = gson.toJson(users);
System.out.println(json);
// [{"name":"Alice","age":25,"active":true},{"name":"Bob","age":30,"active":false}]
// 反序列化清單
Type userListType = new TypeToken<List<User>>(){}.getType();
List<User> users2 = gson.fromJson(json, userListType);
System.out.println(users2.get(0).getName()); // Alice
}
}
重要細節:要反序列化集合請使用 TypeToken<>!
3. 設定 Gson:GsonBuilder
Gson 透過 GsonBuilder 提供彈性設定。你可以啟用 pretty printing、序列化 null、自訂日期格式,等等。
範例:pretty printing 與序列化 null
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class GsonBuilderExample {
public static void main(String[] args) {
User user = new User("Charlie", 0, false);
Gson gson = new GsonBuilder()
.setPrettyPrinting() // 美化輸出(縮排)
.serializeNulls() // 序列化為 null 的欄位
.create();
String json = gson.toJson(user);
System.out.println(json);
/*
{
"name": "Charlie",
"age": 0,
"active": false
}
*/
}
}
日期格式化
如果你有 Date 型別的欄位,預設 Gson 會以特定格式序列化。你可以指定自己的格式:
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.util.Date;
public class DateExample {
private String event;
private Date date;
public DateExample(String event, Date date) {
this.event = event;
this.date = date;
}
}
public class Main {
public static void main(String[] args) {
DateExample meeting = new DateExample("Team Meeting", new Date());
Gson gson = new GsonBuilder()
.setDateFormat("yyyy-MM-dd HH:mm:ss")
.create();
String json = gson.toJson(meeting);
System.out.println(json);
// {"event":"Team Meeting","date":"2024-06-10 13:45:23"}
}
}
4. Gson 註解:控制序列化
Gson 支援註解,以更精確地控制序列化與反序列化。
@SerializedName — 重新命名欄位
若你希望 JSON 中的欄位名稱不同,請使用 @SerializedName:
import com.google.gson.annotations.SerializedName;
public class User {
@SerializedName("full_name")
private String name;
private int age;
private boolean active;
public User(String name, int age, boolean active) {
this.name = name;
this.age = age;
this.active = active;
}
}
現在在序列化時,該欄位會命名為 full_name:
User user = new User("Diana", 28, true);
String json = new Gson().toJson(user);
// {"full_name":"Diana","age":28,"active":true}
@Expose — 僅序列化被標註的欄位
如果你只想序列化特定欄位,請使用 @Expose,並據此設定 Gson:
import com.google.gson.annotations.Expose;
public class User {
@Expose
private String name;
@Expose
private int age;
private boolean active; // 不會被序列化
public User(String name, int age, boolean active) {
this.name = name;
this.age = age;
this.active = active;
}
}
建立啟用 @Expose 支援的 Gson:
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
Gson gson = new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.create();
User user = new User("Eve", 21, false);
String json = gson.toJson(user);
// {"name":"Eve","age":21}
@Since/@Until — 依版本條件式序列化
可以使用 @Since 與 @Until 僅在特定版本序列化欄位(實務上不常用,但了解很有幫助)。
5. Gson 的特性與限制
處理巢狀物件
Gson 能輕鬆處理巢狀物件:
public class Profile {
private User user;
private String bio;
public Profile(User user, String bio) {
this.user = user;
this.bio = bio;
}
}
Profile profile = new Profile(new User("Frank", 27, true), "Java developer");
String json = new Gson().toJson(profile);
// {"user":{"name":"Frank","age":27,"active":true},"bio":"Java developer"}
處理集合
序列化集合(List、Map)沒有問題,但在反序列化時請使用 TypeToken(見上文)。
Gson 相較於 Jackson 的限制
- 在較新的版本之前不支援 Java record 類
- 對新的日期時間 API 支援有限(例如 LocalDate、LocalDateTime——需要自訂配接器)
- 無法使用 Jackson 的註解
- 對複雜的多型結構沒有「開箱即用」支援
- 沒有自動支援雙向引用(循環關係)
自訂配接器(TypeAdapter)
若標準功能不足,可以撰寫自己的配接器來處理複雜型別的序列化/反序列化。
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
public class BooleanAsIntAdapter extends TypeAdapter<Boolean> {
@Override
public void write(JsonWriter out, Boolean value) throws IOException {
out.value(value ? 1 : 0);
}
@Override
public Boolean read(JsonReader in) throws IOException {
return in.nextInt() == 1;
}
}
// 使用方式:
Gson gson = new GsonBuilder()
.registerTypeAdapter(Boolean.class, new BooleanAsIntAdapter())
.create();
6. 實作:帶設定的序列化與反序列化
讓我們擴充你的學習用應用程式,加入以 JSON 格式儲存與載入使用者清單的功能。
帶註解的 User 類別
import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.Expose;
public class User {
@Expose
@SerializedName("full_name")
private String name;
@Expose
private int age;
private boolean active; // 不會被序列化
public User(String name, int age, boolean active) {
this.name = name;
this.age = age;
this.active = active;
}
// getters、setters...
}
將使用者清單儲存為 JSON
import java.util.List;
import java.util.Arrays;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class SaveUsers {
public static void main(String[] args) {
List<User> users = Arrays.asList(
new User("Ivan", 23, true),
new User("Olga", 19, false)
);
Gson gson = new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.setPrettyPrinting()
.create();
String json = gson.toJson(users);
System.out.println(json);
/*
[
{
"full_name": "Ivan",
"age": 23
},
{
"full_name": "Olga",
"age": 19
}
]
*/
}
}
從 JSON 載入使用者清單
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.List;
public class LoadUsers {
public static void main(String[] args) {
String json = "[{\"full_name\":\"Ivan\",\"age\":23},{\"full_name\":\"Olga\",\"age\":19}]";
Gson gson = new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.create();
Type userListType = new TypeToken<List<User>>(){}.getType();
List<User> users = gson.fromJson(json, userListType);
for (User user : users) {
System.out.println(user.getName() + " (" + user.getAge() + ")");
}
// Ivan (23)
// Olga (19)
}
}
7. Gson 與 Jackson 的比較
| 特性 | Gson | Jackson |
|---|---|---|
| 易用性 | +++++(非常簡單) | +++(稍難) |
| 程式庫大小 | 小 | 較大 |
| 速度 | 快,但略慢 | 非常快 |
| 彈性 | 中等 | 高(可調選項更多) |
| 註解支援 | 自家(@SerializedName) | 自家(@JsonProperty 等) |
| 新型別支援 | 受限 | 極佳(Java 8+、record) |
| Android 支援 | 極佳 | 良好,但較笨重 |
| 日期處理 | 需透過配接器 | 開箱即用 |
| 多型 | 受限 | 可彈性設定 |
8. 使用 Gson 時的常見錯誤
錯誤 №1:未對集合使用 TypeToken。
如果要反序列化清單或映射,務必使用 TypeToken<>;否則可能得到奇怪的錯誤或空集合。
錯誤 №2:缺少無參數建構子。
Gson 在沒有預設建構子的情況下也能運作,但對於複雜物件的反序列化有時會出錯。若預計要反序列化,建議準備一個無參數建構子。
錯誤 №3:欄位名稱不一致。
如果 JSON 中的欄位叫做 "full_name",而類別中是 "name",沒有加上 @SerializedName("full_name") 就無法繫結到欄位,值會是 null。
錯誤 №4:private 欄位的問題。
Gson 可以序列化 private 欄位,但如果只有 private 欄位且沒有 getter/setter,反序列化有時會遇到問題。最好還是提供 getter 與 setter。
錯誤 №5:日期處理。
預設 Gson 會以不太方便的格式序列化 Date。對於 LocalDate、LocalDateTime 等新型別,若沒有自訂配接器會發生序列化錯誤。
錯誤 №6:未使用 @Expose,卻啟用了 excludeFieldsWithoutExposeAnnotation()。
如果你啟用了 excludeFieldsWithoutExposeAnnotation(),卻沒有用 @Expose 標註欄位,這些欄位就不會被序列化/反序列化——結果可能是空的 JSON,或物件中的值為 null。
GO TO FULL VERSION