JSON 是現代 Web 服務的標準資料交換格式。Java 中將 POJO(實體類)與 JSON 互轉,Jackson 是事實標準庫(Spring Boot 預設整合)。本文展示如何以註解驅動實現零配置、型別安全、高效能的序列化/反序列化,並解決常見的日期、巢狀物件、忽略欄位、多態等實戰問題。
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version> <!-- 檢查最新版 -->
</dependency>
<!-- 模組:JDK 8 日期、JSR310、Kotlin、Scala 等 -->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.17.0</version>
</dependency>
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.io.IOException;
public final class JsonUtils {
private static final ObjectMapper MAPPER = newObjectMapper();
private static ObjectMapper newObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
// 註冊 JDK 8 日期時間模組
mapper.registerModule(new JavaTimeModule());
// 禁用將日期序列化為 timestamp(長整數),改用 ISO-8601 字串
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
// 忽略未知屬性(反序列化時 JSON 含類別無對應欄位不報錯)
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 允許單引號、非引號欄位名(寬鬆模式,視安全性啟用)
// mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
// mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
return mapper;
}
private JsonUtils() {}
/** 物件 → JSON 字串 */
public static String toJson(Object obj) {
try { return MAPPER.writeValueAsString(obj); }
catch (JsonProcessingException e) { throw new RuntimeException("JSON 序列化失敗", e); }
}
/** JSON 字串 → 物件(型別安全) */
public static T fromJson(String json, Class clazz) {
try { return MAPPER.readValue(json, clazz); }
catch (IOException e) { throw new RuntimeException("JSON 反序列化失敗", e); }
}
/** JSON 字串 → 複雜泛型型別(如 List) */
public static T fromJson(String json, JavaType javaType) {
try { return MAPPER.readValue(json, javaType); }
catch (IOException e) { throw new RuntimeException("JSON 反序列化失敗", e); }
}
/** 建構泛型 JavaType 輔助方法 */
public static JavaType getCollectionType(Class> collectionClass, Class>... elementClasses) {
return MAPPER.getTypeFactory().constructParametricType(collectionClass, elementClasses);
}
/** 格式化輸出(開發除錯用) */
public static String toPrettyJson(Object obj) {
try { return MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(obj); }
catch (JsonProcessingException e) { throw new RuntimeException("JSON 格式化失敗", e); }
}
}
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import java.time.LocalDateTime;
import java.util.List;
@JsonIgnoreProperties(ignoreUnknown = true) // 類別層級忽略未知屬性
@JsonInclude(JsonInclude.Include.NON_NULL) // 全域忽略 null 欄位(可被欄位註解覆蓋)
public class User {
@JsonProperty("user_id") // 指定 JSON 欄位名
private Long id;
@JsonProperty("user_name")
private String username;
@JsonIgnore // 完全忽略:不序列化、不反序列化
private String passwordHash;
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY) // 僅反序列化(請求入參),不輸出
private String rawPassword;
@JsonProperty(access = JsonProperty.Access.READ_ONLY) // 僅序列化(回傳),不接收
private String displayName;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
@JsonSerialize(using = LocalDateTimeSerializer.class)
private LocalDateTime createdAt;
@JsonProperty("roles")
private List roleCodes;
@JsonProperty("profile")
private UserProfile profile; // 巢狀物件,自動遞迴轉換
// Getter/Setter 省略(Lombok @Data 可自動生成)
// 必須有無參建構子供 Jackson 實例化
}
| 場景 | 註解/配置 | 說明 |
|---|---|---|
| 欄位名映射 (camelCase ↔ snake_case) | @JsonProperty("snake_case") 或全域 mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE) | 建議全域統一策略,減少欄位級註解 |
| 忽略 null 輸出 | 類別 @JsonInclude(Include.NON_NULL) 或欄位 @JsonInclude(Include.NON_EMPTY) | NON_EMPTY 同時忽略空集合/空字串 |
| 日期時間格式化 | @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone="GMT+8") + JSR310 模組 | 支援 LocalDate/LocalDateTime/Instant/ZonedDateTime |
| 枚舉序列化為名稱而非 ordinal | 枚舉類別加 @JsonFormat(shape=JsonFormat.Shape.STRING) 或 @JsonValue 在 getter 上 | 避免數值序列化導致前後端不一致 |
| 多態反序列化(同一介面多實作) | @JsonTypeInfo(use=Id.NAME, property="type") + @JsonSubTypes | JSON 需包含 type 欄位標識實際類別 |
| 循環引用(雙向關聯) | @JsonManagedReference (父) + @JsonBackReference (子) 或 @JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id") | IdentityInfo 適用於任意深度循環 |
| 動態忽略欄位(視圖) | @JsonView(Views.Public.class) 在欄位/方法上 + mapper.writerWithView(Views.Public.class) | 同一實體不同 API 回傳不同欄位集 |
public class JsonDemo {
public static void main(String[] args) {
User user = new User();
user.setId(1001L);
user.setUsername("john_doe");
user.setRawPassword("secret123"); // 僅寫入
user.setCreatedAt(LocalDateTime.now());
user.setRoleCodes(List.of("ADMIN", "USER"));
user.setProfile(new UserProfile("John", "Doe", "john@example.com"));
// 1. 物件 → JSON
String json = JsonUtils.toJson(user);
System.out.println("JSON:\n" + JsonUtils.toPrettyJson(user));
// 輸出包含:user_id, user_name, displayName, createdAt, roles, profile
// 不包含:passwordHash, rawPassword
// 2. JSON → 物件
String input = "{\"user_id\":1002,\"user_name\":\"jane\",\"raw_password\":\"pwd\",\"created_at\":\"2024-01-15 10:30:00\"}";
User parsed = JsonUtils.fromJson(input, User.class);
System.out.println("Parsed username: " + parsed.getUsername()); // jane
System.out.println("Raw password (write-only): " + parsed.getRawPassword()); // pwd
// displayName 為 null(READ_ONLY)
// 3. 複雜泛型反序列化
String listJson = "[{\"user_id\":1,\"user_name\":\"a\"},{\"user_id\":2,\"user_name\":\"b\"}]";
JavaType type = JsonUtils.getCollectionType(List.class, User.class);
List users = JsonUtils.fromJson(listJson, type);
System.out.println("List size: " + users.size()); // 2
}
}
mapper.enable(MapperFeature.USE_GETTERS_AS_SETTERS) 等,或依賴 jackson-module-afterburner 產生 bytecode 存取器,提升 20-30% 速度。JsonParser/JsonGenerator 串流處理,避免全量載入記憶體。Jackson 以豐富的註解體系、強大的型別系統與卓越效能,成為 Java JSON 處理的首選。掌握核心註解(@JsonProperty、@JsonIgnore、@JsonFormat、@JsonView、@JsonTypeInfo)與 ObjectMapper 關鍵配置,即可應對 95% 以上的序列化場景。建議專案統一封裝 JsonUtils,集中管理命名策略、日期格式、忽略策略,確保前後端協議一致且易於維護。