Gson 處理 List<User>、Map<String, User> 等標準結構已非常成熟,但當 Key 變成自定義對象 (Map<Point, String>)、Value 為泛型集合 (Map<String, List<Student>>) 時,類型擦除導致運行時無法獲取完整泛型資訊,必須借助 TypeToken 手動指定類型。本文將系統講解三大進階場景的完整序列化/反序列化實戰,並揭示 enableComplexMapKeySerialization() 等關鍵配置。
public class Point { private int x, y; public Point(int x, int y) { this.x = x; this.y = y; } // getter/setter/toString 省略 } 預設 Gson 將 Map Key 調用 toString() 序列化為 JSON 字符串 Key (如 "Point@123"),反序列化時無法還原對象。開啟此選項後,Key 將序列化為 JSON 陣列 [[{keyObj}, value], ...],完整保留對象結構。
import com.google.gson.*; import java.util.*; public class GsonMapKeyDemo { public static void main(String[] args) { // 必須開啟複雜 Key 序列化 Gson gson = new GsonBuilder().enableComplexMapKeySerialization().create(); Map<Point, String> map = new LinkedHashMap<>(); map.put(new Point(5, 6), "A"); map.put(new Point(8, 8), "B"); // 序列化 String json = gson.toJson(map); System.out.println("JSON: " + json); // [[{"x":5,"y":6},"A"],[{"x":8,"y":8},"B"]] // 反序列化:TypeToken 捕獲完整泛型類型 Map<Point, String> restored = gson.fromJson(json, new TypeToken<Map<Point, String>>() {}.getType()); for (Point p : restored.keySet()) { System.out.println("Key: " + p + " → Value: " + restored.get(p)); } } } JSON: [[{"x":5,"y":6},"A"],[{"x":8,"y":8},"B"]] Key: Point[x=5,y=6] → Value: A Key: Point[x=8,y=8] → Value: B public class Student { private int id; private String name; private Date birthDay; // getter/setter/toString } public class Teacher { private int id; private String name; private String title; // getter/setter/toString } public class GsonNestedGenericDemo { public static void main(String[] args) { Gson gson = new Gson(); // 構建測試資料 List<Student> students = Arrays.asList( new Student(1, "李坤", new Date()), new Student(2, "曹貴生", new Date()) ); List<Teacher> teachers = Arrays.asList( new Teacher(1, "米先生", "教授"), new Teacher(2, "丁先生", "講師") ); Map<String, Object> map = new LinkedHashMap<>(); map.put("students", students); map.put("teachers", teachers); // 序列化 String json = gson.toJson(map); System.out.println("JSON: " + json); // 反序列化:TypeToken 指定 Map> Map<String, Object> restored = gson.fromJson(json, new TypeToken<Map<String, List<Object>>>() {}.getType()); for (String key : restored.keySet()) { System.out.println("Key: " + key); List<Object> list = (List<Object>) restored.get(key); for (Object obj : list) { System.out.println(" " + obj); } } } } JSON: {"students":[{"id":1,"name":"李坤","birthDay":"Jun 22, 2024..."},{"id":2,"name":"曹貴生","birthDay":"Jun 22, 2024..."}],"teachers":[{"id":1,"name":"米先生","title":"教授"},{"id":1,"name":"丁先生","title":"講師"}]} Key: students [Student[id=1,name=李坤,...], Student[id=2,name=曹貴生,...]] Key: teachers [Teacher[id=1,name=米先生,...], Teacher[id=2,name=丁先生,...]] Gson gson = new GsonBuilder() .setDateFormat("yyyy-MM-dd HH:mm:ss") // 統一日期格式 .enableComplexMapKeySerialization() .create(); public class GsonAdvancedMapDemo { public static void main(String[] args) { Gson gson = new GsonBuilder() .setDateFormat("yyyy-MM-dd HH:mm:ss") .enableComplexMapKeySerialization() .create(); Map<Point, List<Student>> complexMap = new LinkedHashMap<>(); complexMap.put(new Point(1, 1), Arrays.asList(new Student(1, "A", new Date()))); complexMap.put(new Point(2, 2), Arrays.asList(new Student(2, "B", new Date()))); String json = gson.toJson(complexMap); System.out.println(json); // 反序列化 Map<Point, List<Student>> restored = gson.fromJson(json, new TypeToken<Map<Point, List<Student>>>() {}.getType()); System.out.println("恢復成功: " + restored.size() + " 組"); } } | 場景 | 關鍵配置 | TypeToken 寫法 | 注意事項 |
|---|---|---|---|
| Map<自定義Key, 值> | enableComplexMapKeySerialization() | new TypeToken<Map<Key, Value>>(){}.getType() | Key 類需正確實作 equals/hashCode |
| Map<String, List<泛型>> | 無特殊配置 | new TypeToken<Map<String, List<T>>>(){}.getType() | 反序列化後 Value 為 List |
| Map<Key, List<T>> | enableComplexMapKeySerialization | new TypeToken<Map<Key, List<T>>>(){}.getType() | 雙重泛型嵌套,TypeToken 必須完整捕獲 |
| 日期格式統一 | setDateFormat("yyyy-MM-dd HH:mm:ss") | 無 | 避免預設 Date 格式跨平台不一致 |
new TypeToken<...>(){}.getType() 利用匿名類保留泛型類型資訊,切勿寫成 TypeToken.getParameterized(Map.class, Key.class, Value.class) (後者無法處理巢狀泛型)。JsonReader/JsonWriter 避免全量載入記憶體。enableComplexMapKeySerialization,舊代碼升級必須顯式開啟。Gson 處理複雜 Map 結構的核心在於:
① enableComplexMapKeySerialization() 讓自定義 Key 完整序列化
② TypeToken 匿名內部類捕獲完整泛型類型資訊
③ LinkedHashMap 保證順序穩定
掌握這三點,Map<Point, List<Student>> 這樣任意深度嵌套結構也能輕鬆序列化/反序列化。建議專案封裝 GsonUtils 統一配置,業務代碼一行調用,避免重複造輪子。