1. Jackson 簡介
在 Java 應用程式與外部世界之間的資料交換,常可歸結為這個問題:「如何將 Java 物件轉換為 JSON——再轉回來?」當然,你可以自己用 String.split 和正則表達式寫一個剖析器(甚至從苦痛中獲得樂趣),但在真實專案中沒有人這麼做。
在 Java 中有數個常見的 JSON 函式庫。其中最主要的是 Jackson。它受歡迎到被納入 Spring Boot,並被許多其他框架與函式庫採用。
什麼是 Jackson?
Jackson 是一個功能強大且彈性的函式庫,用於序列化(將 Java 物件轉為 JSON)與反序列化(相反的操作)。它由多個模組組成,但在 90% 的情境下你只需要兩個:
- jackson-core — 核心,低階剖析器。
- jackson-databind — 高階模組,能把 Java 物件轉為 JSON,反之亦然。
你只要記住:如果你看到類別 ObjectMapper ——那就是 Jackson。
Jackson 被視為在 Java 中處理 JSON 的事實標準,因為它兼具簡潔與強大。為了序列化或反序列化資料,往往只需要幾行程式碼,對新手也相當友善。同時它也不僅止於基本功能:透過註解與眾多設定,你可以靈活地控管資料如何轉換為物件與反向處理,無論是集合、巢狀實體,或是各種格式的日期。
同樣重要的是,Jackson 的效能很快且資源使用節省,這對於大量資料的真實專案至關重要。函式庫的開發者持續支援新的 Java 版本與 JSON 標準的最新變動——Jackson 對於從簡單應用到大型企業系統,都是可靠的選擇。
2. 讀取 JSON(反序列化)
讓我們試著讀取一段 JSON 字串並把它轉成 Java 物件。為此需要:
- 資料類別(例如,Person)
- 來自 Jackson 的 ObjectMapper 類別
引入 Jackson
若使用 Maven,請在 pom.xml 中加入:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version>
</dependency>
若使用 Gradle — 類似:
implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.0'
類別範例
public class Person {
public String name;
public int age;
}
注意:為求簡單,欄位設為 public。稍後我們會討論如何處理 private 欄位與 getter/setter。
JSON 範例
{
"name": "Alice",
"age": 30
}
反序列化:把 JSON 轉成物件
import com.fasterxml.jackson.databind.ObjectMapper;
public class Main {
public static void main(String[] args) throws Exception {
String json = "{\"name\": \"Alice\", \"age\": 30}";
ObjectMapper mapper = new ObjectMapper();
Person person = mapper.readValue(json, Person.class);
System.out.println(person.name); // Alice
System.out.println(person.age); // 30
}
}
在此,Jackson 會剖析 JSON 字串,尋找與類別中的欄位或 getter 同名的鍵,並透過 readValue 將對應的值填入物件。
反序列化物件清單
假設我們有一個陣列:
[
{ "name": "Bob", "age": 22 },
{ "name": "Eve", "age": 27 }
]
將其反序列化為 List:
import com.fasterxml.jackson.core.type.TypeReference;
// ...
String json = "[{\"name\": \"Bob\", \"age\": 22}, {\"name\": \"Eve\", \"age\": 27}]";
ObjectMapper mapper = new ObjectMapper();
List<Person> people = mapper.readValue(json, new TypeReference<List<Person>>() {});
for (Person p : people) {
System.out.println(p.name + " (" + p.age + ")");
}
你也許會問:為什麼不能直接寫 mapper.readValue(json, List.class)? 記得我們剛提到 Java 的泛型在編譯時會被擦除嗎?因此我們需要 TypeReference,讓 Jackson 理解清單中的元素型別是 Person。
3. 寫入 JSON(序列化)
現在我們來做相反的操作:把 Java 物件轉成 JSON 字串。
範例
ObjectMapper mapper = new ObjectMapper();
Person person = new Person();
person.name = "Charlie";
person.age = 40;
String json = mapper.writeValueAsString(person);
System.out.println(json);
// {"name":"Charlie","age":40}
序列化物件清單
ObjectMapper mapper = new ObjectMapper();
List<Person> people = new ArrayList<>();
people.add(new Person("Anna", 25));
people.add(new Person("Dmitry", 31));
String json = mapper.writeValueAsString(people);
System.out.println(json);
// [{"name":"Anna","age":25},{"name":"Dmitry","age":31}]
寫入檔案
ObjectMapper mapper = new ObjectMapper();
Person person = new Person();
person.name = "Charlie";
person.age = 40;
mapper.writeValue(new File("person.json"), person);
// 檔案 person.json 現在包含 JSON 物件
美觀的(pretty)JSON
預設情況下,Jackson 會把內容寫成一行。不過也有更「以人為本」的寫法——pretty printing。也就是以可讀性佳的方式輸出 JSON 字串:帶縮排、換行與整齊的格式。
和為了緊湊而多半寫成單行的「一般」JSON 不同,「美觀」版本是給人看的——便於在日誌、檔案或畫面上輕鬆檢視資料結構。
ObjectMapper mapper = new ObjectMapper();
Person person = new Person();
person.name = "Charlie";
person.age = 40;
String prettyJson = mapper.writerWithDefaultPrettyPrinter()
.writeValueAsString(person);
System.out.println(prettyJson);
/*
{
"name" : "Charlie",
"age" : 40
}
*/
4. Jackson 註解
Jackson 支援多種註解,能用來控制序列化與反序列化流程。以下是最實用的幾個:
@JsonProperty
當 JSON 中的欄位名稱與類別中的欄位名稱不同時,允許你指定 JSON 的欄位名稱。
import com.fasterxml.jackson.annotation.JsonProperty;
public class Person {
@JsonProperty("full_name")
public String name;
public int age;
}
{"full_name": "Olga", "age": 28}
Jackson 會理解,JSON 中的 full_name 欄位要寫入物件的 name 欄位。
@JsonIgnore
如果你不想序列化或反序列化某個欄位:
import com.fasterxml.jackson.annotation.JsonIgnore;
public class Person {
public String name;
@JsonIgnore
public int age;
}
即使物件中有該欄位,JSON 中也不會出現 age。
@JsonInclude
控制哪些欄位會進入 JSON。例如,只序列化非空欄位:
import com.fasterxml.jackson.annotation.JsonInclude;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Person {
public String name;
public Integer age;
}
如果 age == null,JSON 中不會有 "age" 這個鍵。
@JsonFormat
允許為日期與時間指定序列化格式。
import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Date;
public class Event {
public String title;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
public Date date;
}
Event event = new Event();
event.title = "Hackathon";
event.date = new Date();
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(event);
// {"title":"Hackathon","date":"2024-06-07 15:23:00"}
範例:整合
import com.fasterxml.jackson.annotation.*;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Person {
@JsonProperty("full_name")
private String name;
private int age;
@JsonIgnore
private String password;
// private 欄位必須提供 getter 與 setter!
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}
5. 實作練習:考量註解的序列化與反序列化
讓我們擴充你的學習用應用程式——現在有一個 User 類別,包含 private 欄位、註冊日期,以及不應該出現在 JSON 裡的密碼。
import com.fasterxml.jackson.annotation.*;
import java.util.Date;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class User {
@JsonProperty("login")
private String username;
private int age;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private Date registered;
@JsonIgnore
private String password;
// 必須提供 getter 與 setter!
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public Date getRegistered() { return registered; }
public void setRegistered(Date registered) { this.registered = registered; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
}
序列化
import com.fasterxml.jackson.databind.ObjectMapper;
import java.text.SimpleDateFormat;
ObjectMapper mapper = new ObjectMapper();
User user = new User();
user.setUsername("superuser");
user.setAge(42);
user.setRegistered(new SimpleDateFormat("yyyy-MM-dd").parse("2024-06-07"));
user.setPassword("qwerty123"); // 不會出現在 JSON 中!
String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(user);
System.out.println(json);
/*
{
"login" : "superuser",
"age" : 42,
"registered" : "2024-06-07"
}
*/
反序列化
import com.fasterxml.jackson.databind.ObjectMapper;
ObjectMapper mapper = new ObjectMapper();
String json = "{ \"login\": \"superuser\", \"age\": 42, \"registered\": \"2024-06-07\" }";
User user = mapper.readValue(json, User.class);
System.out.println(user.getUsername()); // superuser
System.out.println(user.getAge()); // 42
System.out.println(user.getRegistered());// Fri Jun 07 00:00:00 ...
System.out.println(user.getPassword()); // null(這很好!)
6. 使用 Jackson 的常見錯誤
錯誤 1:缺少無參數建構子。
若類別沒有無參數建構子,Jackson 將無法建立該類別的物件。這常見於只明確宣告了帶參數建構子的類別。
錯誤 2:private 欄位沒有 getter/setter。
如果你把所有欄位都設為 private,卻忘了提供 getter 與 setter,Jackson 在預設情況下無法於反序列化時填入它們。
錯誤 3:欄位名稱不一致。
當 JSON 中的欄位名與 Java 的欄位或 getter 名稱不同,Jackson 找不到對應。請使用 @JsonProperty。
錯誤 4:日期格式不正確。
如果 JSON 中的日期格式與 Java 端預期的格式不一致,Jackson 會拋出剖析錯誤。請使用 @JsonFormat 進行設定。
錯誤 5:嘗試序列化帶有 @JsonIgnore 註解的欄位。
這些欄位不會出現在 JSON 中——這不是 bug,而是設計。
錯誤 6:在集合的序列化/反序列化中未指定元素型別。
如果不使用 TypeReference,Jackson 不會知道集合中的物件型別。
錯誤 7:讀寫檔案時發生例外。
處理檔案時,別忘了處理 IOException。
GO TO FULL VERSION