Gson 是 Google 開源的 Java JSON 庫,以「零配置、支援泛型、無需註解、效能優異」著稱。本文面向初學者,從 Maven 引入、基本實體定義、toJson/fromJson 核心 API、單對象/列表/Map 轉換、TypeToken 處理泛型擦除、日期格式化、常見異常處理等全方位講解,附完整可運行代碼。
<dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.10.1</version> </dependency> public class Person { private String name; private int age; private List<String> hobbies; private Address address; // getter/setter/toString 省略 // 內部類 Address public static class Address { private String city; private String street; // getter/setter } } Gson gson = new Gson(); Person p = new Person(); p.setName("張三"); p.setAge(25); p.setHobbies(Arrays.asList("閱讀", "游泳")); p.setAddress(new Address("台北", "信義路")); String json = gson.toJson(p); System.out.println(json); {"name":"張三","age":25,"hobbies":["閱讀","游泳"],"address":{"city":"台北","street":"信義路"}} String json = "{\"name\":\"李四\",\"age\":30,\"hobbies\":[\"跑步\"]}"; Person p2 = gson.fromJson(json, Person.class); System.out.println(p2.getName()); // 李四 List<Person> list = Arrays.asList(p, p2); String jsonList = gson.toJson(list); // [{"name":"張三",...},{"name":"李四",...}] String jsonList = "[{\"name\":\"王五\",\"age\":20}]"; // ❌ 錯誤:gson.fromJson(jsonList, List.class) → List // ✅ 正確:TypeToken 捕獲泛型類型 List<Person> restored = gson.fromJson(jsonList, new TypeToken<List<Person>>() {}.getType()); Map<String, Person> map = new LinkedHashMap<>(); map.put("user1", p); map.put("user2", p2); String jsonMap = gson.toJson(map); // {"user1":{...},"user2":{...}} // 反序列化 Map<String, Person> restoredMap = gson.fromJson(jsonMap, new TypeToken<Map<String, Person>>() {}.getType()); Java 泛型在編譯期經過類型擦除,運行時 List<Person> 僅剩 List。Gson 無法從 List.class 知道元素類型,故反序列化為 List<LinkedTreeMap> (內部預設 Map 實現)。
// 匿名內部類繼承 TypeToken,利用繼承關係保留泛型簽名 TypeToken<List<Person>> token = new TypeToken<List<Person>>() {}; Type type = token.getType(); // 運行時可獲取完整 ParameterizedType 這是「超級類型令牌」模式,Gson 內部通過反射讀取匿名類的泛型父類簽名,還原完整類型資訊。
Gson gson = new GsonBuilder() .setPrettyPrinting() // 格式化輸出 (縮排、換行) .serializeNulls() // 輸出 null 欄位 (預設忽略) .setDateFormat("yyyy-MM-dd HH:mm:ss") // 統一日期格式 .disableHtmlEscaping() // 不轉義 HTML 字符 (< > &) .enableComplexMapKeySerialization() // 支援非 String Key Map .create(); | 異常 | 原因 | 解決 |
|---|---|---|
| JsonSyntaxException: Expected BEGIN_OBJECT but was BEGIN_ARRAY | JSON 結構與目標類不匹配 (如傳陣列給單對象) | 檢查 JSON 結構、調整目標類型 (List/Map/陣列) |
| JsonParseException: Date format | 日期字符串不符合預設/配置格式 | 統一 setDateFormat 或自訂 JsonDeserializer |
| StackOverflowError | 對象循環引用 (A→B→A) | 使用 @Expose 控制序列化欄位、或改用 Jackson @JsonIgnore |
| 反序列化後 List 元素為 LinkedTreeMap | 未使用 TypeToken | 必須 new TypeToken<List<T>>(){}.getType() |
public class GsonQuickStart { public static void main(String[] args) { Gson gson = new GsonBuilder().setPrettyPrinting().create(); // 1. 單對象 Person p = new Person("Alice", 28, Arrays.asList("Java", "Music")); String json = gson.toJson(p); System.out.println("=== toJson ==="); System.out.println(json); // 2. 反序列化 Person p2 = gson.fromJson(json, Person.class); System.out.println("=== fromJson ==="); System.out.println(p2); // 3. 列表 List<Person> list = Arrays.asList(p, p2); String listJson = gson.toJson(list); List<Person> restored = gson.fromJson(listJson, new TypeToken<List<Person>>(){}.getType()); System.out.println("=== List 恢復 ==="); restored.forEach(System.out::println); } } @JsonIgnore, @JsonProperty)、多態序列化、模組化擴展,Jackson 是企業級標準選擇,遷移成本低 (API 相似)。JsonUtils 統一 GsonBuilder 配置,業務代碼一行調用 JsonUtils.toJson(obj)/JsonUtils.fromJson(json, clazz)。掌握 toJson/fromJson + TypeToken 這組核心組合,即可輕鬆應對 90% 的 JSON 處理需求。開始在你的專案中引入 Gson,享受「Java 對象自由穿梭 JSON 世界」的便利吧!