Java JSON 處理實戰:Gson 解析、生成與複雜數據結構轉換完全指南
JSON 核心語法回顧
- 對象:
{"key": "value"},鍵值對集合,鍵必須雙引號。
- 數組:
[1, "two", {"nested": true}],有序值序列。
- 值類型:字符串、數字、布爾、null、對象、數組。
為什麼選 Gson?
- Google 開源,成熟穩定,Android 系統級依賴。
- 零依賴,Jar 體積小(~200KB)。
- 支持泛型、泛型類型擷取(
TypeToken)、自定義序列化/反序列化器。
- 默認配置開箱即用,亦可精細調控(字段命名策略、null 處理、日期格式等)。
Maven 依賴
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version> <!-- 檢查最新版 -->
</dependency>
核心 API 速覽
| 類/方法 | 用途 |
new Gson() | 創建實例,可鏈式配置 |
toJson(obj) | 對象 → JSON 字符串 |
fromJson(json, Type) | JSON → 對象(需傳遞 Type) |
GsonBuilder | 構建器模式,配置美化輸出、null 序列化、日期格式等 |
TypeToken<T>{}.getType() | 獲取泛型類型(如 List<User>) |
JsonParser.parseString() | 解析為 JsonElement 樹模型(非綁定 POJO) |
場景一:JSON 字符串反序列化為 POJO
// employee.json
{
"department": "design",
"employees": [
{"firstName": "Bill", "lastName": "Gates"},
{"firstName": "George", "lastName": "Bush"},
{"firstName": "Thomas", "lastName": "Carter"}
]
}
// POJO
@Data @AllArgsConstructor @NoArgsConstructor
class Employee { String firstName, lastName; }
@Data @AllArgsConstructor @NoArgsConstructor
class DeptData { String department; List<Employee> employees; }
// 解析
Gson gson = new Gson();
DeptData data = gson.fromJson(new FileReader("employee.json"), DeptData.class);
System.out.println(data.getDepartment()); // design
data.getEmployees().forEach(e -> System.out.println(e.getFirstName())); // Bill, George, Thomas
場景二:泛型集合反序列化(TypeToken)
String jsonArray = "[{\"id\":1,\"name\":\"Java\"},{\"id\":2,\"name\":\"Python\"}]";
Type listType = new TypeToken<List<Language>>(){}.getType();
List<Language> langs = gson.fromJson(jsonArray, listType);
場景三:流式解析大文件(低內存)
try (JsonReader reader = new JsonReader(new FileReader("huge.json"))) {
reader.beginArray();
while (reader.hasNext()) {
Language lang = gson.fromJson(reader, Language.class);
process(lang); // 逐條處理,不載入全量
}
reader.endArray();
}
場景四:生成 JSON(對象 → 字符串/文件)
Gson gson = new GsonBuilder()
.setPrettyPrinting() // 縮進格式化
.serializeNulls() // 輸出 null 字段
.setDateFormat("yyyy-MM-dd") // 日期格式
.create();
Map<String, Object> data = new LinkedHashMap<>();
data.put("cat", "it");
data.put("languages", List.of(
Map.of("id", 1, "name", "Java", "ide", "IntelliJ"),
Map.of("id", 2, "name", "Go", "ide", "GoLand")
));
data.put("pop", true);
String json = gson.toJson(data);
Files.writeString(Paths.get("output.json"), json, StandardCharsets.UTF_8);
輸出:
{
"cat": "it",
"languages": [
{"id": 1, "name": "Java", "ide": "IntelliJ"},
{"id": 2, "name": "Go", "ide": "GoLand"}
],
"pop": true
}
場景五:樹模型操作(無 POJO)
JsonElement root = JsonParser.parseString(jsonStr);
JsonObject obj = root.getAsJsonObject();
String dept = obj.get("department").getAsString();
JsonArray arr = obj.getAsJsonArray("employees");
for (JsonElement e : arr) {
JsonObject emp = e.getAsJsonObject();
System.out.println(emp.get("firstName").getAsString());
}
進階配置:GsonBuilder 常用選項
Gson gson = new GsonBuilder()
.disableHtmlEscaping() // 不轉義 HTML 字符(< > &)
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) // 駝峰轉下劃線
.excludeFieldsWithoutExposeAnnotation() // 僅序列化 @Expose 字段
.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter()) // 自定義類型適配器
.setLenient() // 寬鬆模式(允許單引號、尾隨逗號等)
.create();
常見問題與避坑
| 問題 | 原因 | 解決 |
泛型擦除導致反序列化為 LinkedTreeMap | 運行時丟失泛型信息 | 使用 TypeToken 傳遞完整類型 |
| 日期字段序列化為時間戳/非預期格式 | 默認策略 | setDateFormat 或註冊 TypeAdapter |
| null 字段不輸出 / 報錯 | 默認不序列化 null | serializeNulls() 或字段註解 @SerializedName(..., nullSafe=true) |
| 循環引用棧溢出 | 雙向關聯 | @Expose 控制 / 自定義適配器斷開循環 |
| 大數字精度丟失 | JSON 數字無精度定義,Gson 映射為 double | 字段定義為 String / BigDecimal + 適配器 |
對比:Gson vs Jackson vs Fastjson
| 維度 | Gson | Jackson | Fastjson |
| 生態地位 | Android 標配,Spring Boot 可選 | Spring Boot 默認,功能最全 | 阿里開源,國內早期流行 |
| 性能 | 中等 | 高(流式+後端優化) | 高(但有安全史) |
| 易用性 | 極簡 API | 豐富但複雜 | 兼容 Gson/Jackson API |
| 安全性 | 良好 | 良好(及時修復 CVE) | 歷史漏洞多,需升級 |
| 推薦場景 | Android、輕量微服務、簡單轉換 | 企業級微服務、複雜序列化需求 | 存量項目維護、兼容舊代碼 |
總結
Gson 以極簡 API、零依賴、穩定可靠成為 Java JSON 處理的首選之一。掌握 toJson/fromJson 核心流、TypeToken 泛型處理、GsonBuilder 精細配置、流式解析大文件與樹模型動態操作,足以應對 95% 以上業務場景。對於高性能、複雜多態、安全敏感場景,建議遷移至 Jackson。