Jackson 是 Java 生態中事實標準的 JSON 處理庫,核心類 ObjectMapper 提供了序列化、反序列化、樹模型、流式 API 三種範式。本文系統講解三種模式用法、泛型反序列化、日期/枚舉/空值處理、註解定制、效能優化與常見坑點。
ObjectMapper mapper = new ObjectMapper()
// 常用全域配置
.configure(SerializationFeature.INDENT_OUTPUT, true) // 格式化輸出
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) // 忽略未知欄位
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) // 日期輸出為 ISO-8601 字串
.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")) // 自定義日期格式
.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai")) // 時區
.registerModule(new JavaTimeModule()) // 支援 Java 8 LocalDateTime 等
.setSerializationInclusion(JsonInclude.Include.NON_NULL); // 忽略 null 值
@Data // Lombok
@NoArgsConstructor @AllArgsConstructor
public class User {
private Long id;
private String name;
private String email;
private int age;
@JsonFormat(pattern = "yyyy-MM-dd") // 欄位級日期格式
private LocalDate birthday;
@JsonProperty("is_vip") // JSON 鍵名映射
private boolean vip;
@JsonIgnore // 序列化忽略
private String internalToken;
}
User user = new User(1L, "小明", "xm@example.com", 20, LocalDate.of(2004, 1, 1), true, "secret");
// 1. 輸出字串
String json = mapper.writeValueAsString(user);
// {"id":1,"name":"小明","email":"xm@example.com","age":20,"birthday":"2004-01-01","is_vip":true}
// 2. 輸出檔案/流
mapper.writeValue(new File("user.json"), user);
mapper.writeValue(System.out, user);
// 3. 集合序列化
List<User> users = Arrays.asList(user, new User(2L, "小紅", ...));
String jsonList = mapper.writeValueAsString(users);
String json = "{\"id\":1,\"name\":\"小明\",\"age\":20,\"birthday\":\"2004-01-01\",\"is_vip\":true}";
// 1. 簡單類別
User user = mapper.readValue(json, User.class);
// 2. 泛型集合:需用 TypeReference
List<User> users = mapper.readValue(jsonList, new TypeReference<List<User>>() {});
// 3. 陣列
User[] arr = mapper.readValue(jsonList, User[].class);
// 4. 從檔案/流讀取
User u = mapper.readValue(new File("user.json"), User.class);
| 模式 | 核心類 | 特點 | 適用場景 |
|---|---|---|---|
| Data Binding | ObjectMapper | 最高級抽象,POJO 自動映射,開發效率最高 | 常規業務開發、REST API、配置文件 |
| Tree Model | JsonNode, ObjectNode | 動態結構,無需預定義 POJO,靈活遍歷/修改 | 動態 JSON、Schema 不固定、中間層轉換 |
| Streaming API | JsonParser, JsonGenerator | 最低級、零拷貝、記憶體佔用極小、效能最高 | 超大 JSON、高吞吐場景、嵌入式環境 |
String json = "{\"name\":\"root\",\"children\":[{\"id\":1},{\"id\":2}]}";
// 1. 解析為樹
JsonNode root = mapper.readTree(json);
// 2. 安全取值
String name = root.path("name").asText(); // 找不到返回空字串
JsonNode child0 = root.path("children").get(0); // 陣列索引
int id = child0.path("id").asInt();
// 3. 構建樹
ObjectNode newRoot = mapper.createObjectNode();
newRoot.put("status", "ok");
ArrayNode arr = mapper.createArrayNode();
arr.add(1).add(2).add(3);
newRoot.set("data", arr);
// 4. 樹轉 POJO / POJO 轉樹
User u = mapper.treeToValue(root, User.class);
JsonNode tree = mapper.valueToTree(user);
try (JsonGenerator gen = mapper.getFactory().createGenerator(new File("out.json"), JsonEncoding.UTF8)) {
gen.writeStartObject();
gen.writeStringField("name", "Speedment");
gen.writeNumberField("version", 3);
gen.writeFieldName("features");
gen.writeStartArray();
gen.writeString("Stream API");
gen.writeString("Code Generation");
gen.writeEndArray();
gen.writeEndObject();
}
try (JsonParser parser = mapper.getFactory().createParser(new File("out.json"))) {
while (parser.nextToken() != JsonToken.END_OBJECT) {
String field = parser.getCurrentName();
if ("name".equals(field)) {
parser.nextToken(); // 移動到值
System.out.println(parser.getText());
} else if ("features".equals(field)) {
parser.nextToken(); // START_ARRAY
while (parser.nextToken() != JsonToken.END_ARRAY) {
System.out.println(parser.getText());
}
}
}
}
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes({@Type(value = Cat.class, name = "cat"), @Type(value = Dog.class, name = "dog")})
interface Animal { }
{"type":"cat","name":"Kitty"} → 自動反序列化為 Cat 實例
public class LocalDateSerializer extends JsonSerializer<LocalDate> {
@Override public void serialize(LocalDate d, JsonGenerator g, SerializerProvider p) throws IOException {
g.writeString(d.format(DateTimeFormatter.ISO_LOCAL_DATE));
}
}
// 使用:@JsonSerialize(using = LocalDateSerializer.class)
@JsonManagedReference // 父端 private List<Child> children; @JsonBackReference // 子端 private Parent parent; // 或使用 @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
@JsonIgnoreProperties(ignoreUnknown = true) // 類級
@JsonAnySetter // 捕獲動態屬性存入 Map
public void setDynamic(String key, Object value) { dynamicProps.put(key, value); }
mapper.registerModule(new AfterburnerModule()),字節碼生成存取器,提速 20%-30%。@JsonView) 或 DTO 截斷不必要關聯。mapper.readValues(parser, User.class) 逐條讀取,不載入全量記憶體。FAIL_ON_UNKNOWN_PROPERTIES=false, WRITE_DATES_AS_TIMESTAMPS=false 等。| 問題 | 原因 | 解決 |
|---|---|---|
| 日期輸出為時間戳 | 預設 WRITE_DATES_AS_TIMESTAMPS=true | 關閉該特性 + 註冊 JavaTimeModule + @JsonFormat |
| 反序列化報 UnrecognizedPropertyException | JSON 含 POJO 無的欄位 | FAIL_ON_UNKNOWN_PROPERTIES=false 或 @JsonIgnoreProperties |
| LocalDateTime 無法反序列化 | 缺少 JavaTimeModule | mapper.registerModule(new JavaTimeModule()) |
| 泛型集合反序列化為 LinkedHashMap | 類型擦除,未提供 TypeReference | new TypeReference<List<User>>(){} 或 JavaType |
| 循環引用 StackOverflowError | 雙向關聯無斷點 | @JsonManagedReference/@JsonBackReference 或 @JsonIdentityInfo |
| null 值輸出 "null" | 預設包含 null | setSerializationInclusion(NON_NULL) 或 @JsonInclude |
Jackson 以 ObjectMapper 為核心,提供 Data Binding、Tree Model、Streaming API 三種範式覆蓋從簡單到極致效能的所有場景。掌握註解驅動定制、泛型 TypeReference、日期/多態/循環引用處理、Afterburner 加速、流式大數據處理,即可在生產環境游刃有餘地應對各種 JSON 交互需求。