前後端分離架構下,@ResponseBody 返回對象直接序列化為 JSON,日期字段(Date、LocalDateTime 等)的格式化是高頻剛需。本文系統講解 Jackson 依賴配置、全局 ObjectMapper 定制、@JsonFormat 字段級覆蓋、Java 8 時間模塊支持、常見坑與避坑指南,構建完整日期序列化解決方案。
<properties>
<jackson.version>2.15.2</jackson.version> <!-- 統一版本,避免傳遞依賴衝突 -->
</properties>
<dependencies>
<!-- 核心流式解析/生成 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- 資料綁定:ObjectMapper、註解、POJO 映射 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- Java 8 時間類型支持(LocalDateTime、Instant、ZoneId 等) -->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
jackson-datatype-jsr310
${jackson.version}
注意: Spring Boot 2.x/3.x 內置 Jackson,無需額外引入,直接配置即可。傳統 SpringMVC 需確保類路徑有 jackson-databind,Spring MappingJackson2HttpMessageConverter 會自動檢測並啟用。
@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
// 1. 註冊 Java 8 時間模塊(關鍵!否則 LocalDateTime 序列化為數組/時間戳)
mapper.registerModule(new JavaTimeModule());
// 2. 禁用時間戳輸出,改用 ISO-8601 字串格式(推薦)
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
// 3. 設置全局默認日期格式(針對 java.util.Date、Calendar)
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
// 4. 設置全局時區(避免跨時區部署導致時間偏移)
mapper.setTimeZone(TimeZone.getTimeZone("GMT+8")); // 或 ZoneId.of("Asia/Shanghai")
// 5. 反序列化容錯配置
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // 忽略未知字段
mapper.configure(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE, true); // 時區自適應
return mapper;
}
}
# application.yml
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
serialization:
write-dates-as-timestamps: false
deserialization:
fail-on-unknown-properties: false
# JavaTimeModule 需代碼註冊,或 Spring Boot 3+ 自動註冊
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="com.fasterxml.jackson.databind.ObjectMapper">
<property name="dateFormat">
<bean class="java.text.SimpleDateFormat">
<constructor-arg value="yyyy-MM-dd HH:mm:ss"/>
</bean>
</property>
<property name="timeZone" value="GMT+8"/>
</bean>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
public class OrderVO {
// 1. 標準格式:序列化/反序列化雙向生效
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime createTime;
// 2. 僅序列化輸出格式化(反序列化按全局/默認)
@JsonFormat(pattern = "yyyy/MM/dd", shape = JsonFormat.Shape.STRING)
private LocalDate shipDate;
// 3. 針對 java.util.Date 同樣生效
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ")
private Date updateTime;
// 4. 枚舉/數字/字串形態控制
@JsonFormat(shape = JsonFormat.Shape.NUMBER_INT) // 序列化為時間戳毫秒數
private Instant instant;
}
| 屬性 | 作用 | 示例 |
|---|---|---|
| pattern | 日期格式模式 | yyyy-MM-dd HH:mm:ss, yyyy-MM-dd'T'HH:mm:ss.SSSXXX |
| timezone | 時區 ID | GMT+8, Asia/Shanghai, UTC |
| locale | 區域設定(月份名稱等) | zh_CN, US |
| shape | 輸出形態 | STRING(默認), NUMBER_INT(時間戳), ARRAY |
public class Event {
private LocalDateTime startTime; // 本地日期時間,無時區
private ZonedDateTime zonedTime; // 帶時區日期時間
private Instant timestamp; // 時間戳(UTC),資料庫存儲/傳輸首選
private LocalDate dateOnly; // 僅日期
private LocalTime timeOnly; // 僅時間
private Duration duration; // 時長
private Period period; // 日期間隔
}
否則 LocalDateTime 會被序列化為 [2023, 10, 1, 14, 30, 0] 數組,Instant 序列化為時間戳數字,反序列化同樣失敗。
Date/Calendar:SimpleDateFormat 模式(yyyy-MM-dd HH:mm:ss)。java.time 類型:DateTimeFormatter 模式(yyyy-MM-dd HH:mm:ss 兼容,但推薦 ISO-8601 yyyy-MM-dd'T'HH:mm:ss)。| 現象 | 原因 | 解決方案 |
|---|---|---|
| 日期輸出為時間戳數字 | WRITE_DATES_AS_TIMESTAMPS=true (默認) | 全局 mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) 或字段 @JsonFormat(shape=STRING) |
| LocalDateTime 序列化為數組 | 缺少 JavaTimeModule | mapper.registerModule(new JavaTimeModule()) |
| 時間少 8 小時 / 多 8 小時 | 時區不一致(JVM/服務器/數據庫/前端) | 全局統一 setTimeZone(GMT+8),字段 timezone 覆蓋,數據庫連接配置 serverTimezone=Asia/Shanghai |
| 反序列化失敗:Unrecognized field | JSON 字段與 POJO 不匹配 | FAIL_ON_UNKNOWN_PROPERTIES=false 或補全字段 |
| @JsonFormat 不生效 | 用在 getter 上但未開啟 USE_GETTERS_AS_SETTERS,或類型不匹配 | 建議標註在字段上;確認類型為 Date/LocalDateTime 等時間類型 |
| SimpleDateFormat 線程不安全 | 全局 setDateFormat 共用實例 | Jackson 內部已處理線程安全,但自定義 DateFormat 實現需注意 |
// 多格式兼容反序列化器
public class FlexibleLocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
private static final DateTimeFormatter[] FORMATTERS = {
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"),
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"),
DateTimeFormatter.ofPattern("yyyy-MM-dd"),
DateTimeFormatter.ISO_LOCAL_DATE_TIME
};
@Override
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String text = p.getText().trim();
for (DateTimeFormatter fmt : FORMATTERS) {
try { return LocalDateTime.parse(text, fmt); }
catch (DateTimeParseException ignored) {}
}
throw new IOException("無法解析日期: " + text);
}
}
// 使用
@JsonDeserialize(using = FlexibleLocalDateTimeDeserializer.class)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime flexibleTime;
jackson-databind + jackson-datatype-jsr310(Java 8+ 必備)。ObjectMapper 配置格式、時區、時間戳開關、容錯策略。@JsonFormat 精細控制單字段格式/形態/時區。java.time 類型,配合 JavaTimeModule。掌握這套體系,即可從容應對 REST API 中 99% 的日期序列化場景,告別「時間少 8 小時」、「格式不一致」、「反序列化報錯」等經典坑點。