知識庫 / Java Dates RSS 訂閱

Jackson 日期處理

Jackson,Java Dates
HongKong
6
10:03 PM · Dec 05 ,2025

1. 概述

本教程將使用 Jackson 對日期進行序列化。首先,我們將序列化一個簡單的 java.util.<em>Date</em> 對象,然後是 Joda-Time,最後是 Java 8 的 <em>DateTime</em> 對象。

2. 將 Date 對象序列化為時間戳

首先,讓我們看看如何使用 Jackson 將簡單的 java.util.Date 對象序列化。

在下面的示例中,我們將序列化一個名為 “Event” 的實例,該實例具有名為 “eventDate” 的 Date 字段:

@Test
public void whenSerializingDateWithJackson_thenSerializedToTimestamp()
  throws JsonProcessingException, ParseException {
 
    SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm");
    df.setTimeZone(TimeZone.getTimeZone("UTC"));

    Date date = df.parse("01-01-1970 01:00");
    Event event = new Event("party", date);

    ObjectMapper mapper = new ObjectMapper();
    mapper.writeValueAsString(event);
}

需要注意的是,Jackson 默認會將 Date 序列化為時間戳格式(自 1970 年 1 月 1 日 UTC 至今的毫秒數)。

event” 序列化的實際輸出是:

{
   "name":"party",
   "eventDate":3600000
}

3. 將 Date 序列化為 ISO-8601 格式

將時間戳序列化為這種簡潔的格式並不理想。相反,我們將 Date 序列化為 ISO-8601 格式:

@Test
public void whenSerializingDateToISO8601_thenSerializedToText()
  throws JsonProcessingException, ParseException {
 
    SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm");
    df.setTimeZone(TimeZone.getTimeZone("UTC"));

    String toParse = "01-01-1970 02:30";
    Date date = df.parse(toParse);
    Event event = new Event("party", date);

    ObjectMapper mapper = new ObjectMapper();
    mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    // StdDateFormat is ISO8601 since jackson 2.9
    mapper.setDateFormat(new StdDateFormat().withColonInTimeZone(true));
    String result = mapper.writeValueAsString(event);
    assertThat(result, containsString("1970-01-01T02:30:00.000+00:00"));
}

我們現在可以看到,日期的表示方式更加易讀。

4. 配置 ObjectMapper DateFormat

前述解決方案在選擇精確格式表示 java.util.Date 實例方面仍缺乏足夠的靈活性。

相反,讓我們來看一個允許我們設置日期表示格式的配置:

@Test
public void whenSettingObjectMapperDateFormat_thenCorrect()
  throws JsonProcessingException, ParseException {
 
    SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm");

    String toParse = "20-12-2014 02:30";
    Date date = df.parse(toParse);
    Event event = new Event("party", date);

    ObjectMapper mapper = new ObjectMapper();
    mapper.setDateFormat(df);

    String result = mapper.writeValueAsString(event);
    assertThat(result, containsString(toParse));
}

請注意,儘管我們現在在日期格式上更加靈活,但整個 ObjectMapper 級別仍然使用全局配置。

5. 使用 @JsonFormat 格式化 Date 對象

接下來,讓我們看看如何使用 @JsonFormat 註解來控制單個類中 Date 對象的格式,而不是全局地為整個應用程序設置格式。

public class Event {
    public String name;

    @JsonFormat
      (shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss")
    public Date eventDate;
}

現在我們來測試一下:

@Test
public void whenUsingJsonFormatAnnotationToFormatDate_thenCorrect()
  throws JsonProcessingException, ParseException {
 
    SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
    df.setTimeZone(TimeZone.getTimeZone("UTC"));

    String toParse = "20-12-2014 02:30:00";
    Date date = df.parse(toParse);
    Event event = new Event("party", date);

    ObjectMapper mapper = new ObjectMapper();
    String result = mapper.writeValueAsString(event);
    assertThat(result, containsString(toParse));
}

6. 自定義日期序列化器

接下來,為了獲得對輸出的完全控制權,我們將使用自定義序列化器處理日期:

public class CustomDateSerializer extends StdSerializer<Date> {
 
    private SimpleDateFormat formatter 
      = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");

    public CustomDateSerializer() {
        this(null);
    }

    public CustomDateSerializer(Class t) {
        super(t);
    }
    
    @Override
    public void serialize (Date value, JsonGenerator gen, SerializerProvider arg2)
      throws IOException, JsonProcessingException {
        gen.writeString(formatter.format(value));
    }
}

現在我們將使用它作為我們“eventDate”字段的序列化器:

public class Event {
    public String name;

    @JsonSerialize(using = CustomDateSerializer.class)
    public Date eventDate;
}

最後,我們將進行測試:

@Test
public void whenUsingCustomDateSerializer_thenCorrect()
  throws JsonProcessingException, ParseException {
 
    SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");

    String toParse = "20-12-2014 02:30:00";
    Date date = df.parse(toParse);
    Event event = new Event("party", date);

    ObjectMapper mapper = new ObjectMapper();
    String result = mapper.writeValueAsString(event);
    assertThat(result, containsString(toParse));
}

7. 使用 Jackson 序列化 Joda-Time

日期並非總是 java.util.Date 的實例。事實上,日期越來越多地由其他類表示,其中一個常見的實現是 Joda-Time 庫中的 DateTime 實現。

讓我們看看如何使用 Jackson 序列化 DateTime

我們將利用 jackson-datatype-joda 模塊,以獲得開箱即用的 Joda-Time 支持:

<dependency>
  <groupId>com.fasterxml.jackson.datatype</groupId>
  <artifactId>jackson-datatype-joda</artifactId>
  <version>2.17.2</version>
</dependency>

然後,我們只需註冊 JodaModule,即可完成:

@Test
public void whenSerializingJodaTime_thenCorrect() 
  throws JsonProcessingException {
    DateTime date = new DateTime(2014, 12, 20, 2, 30, 
      DateTimeZone.forID("Europe/London"));

    ObjectMapper mapper = new ObjectMapper();
    mapper.registerModule(new JodaModule());
    mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

    String result = mapper.writeValueAsString(date);
    assertThat(result, containsString("2014-12-20T02:30:00.000Z"));
}

8. 使用自定義序列器序列化 Joda DateTime

如果不想引入額外的 Joda-Time Jackson 依賴,我們也可以利用 自定義序列器(類似於之前的示例),以獲得乾淨的 DateTime 實例序列化:

public class CustomDateTimeSerializer extends StdSerializer<DateTime> {

    private static DateTimeFormatter formatter = 
      DateTimeFormat.forPattern("yyyy-MM-dd HH:mm");

    public CustomDateTimeSerializer() {
        this(null);
    }

     public CustomDateTimeSerializer(Class<DateTime> t) {
         super(t);
     }
    
    @Override
    public void serialize
      (DateTime value, JsonGenerator gen, SerializerProvider arg2)
      throws IOException, JsonProcessingException {
        gen.writeString(formatter.print(value));
    }
}

然後我們可以將其作為我們的屬性“eventDate”序列化器使用:

public class Event {
    public String name;

    @JsonSerialize(using = CustomDateTimeSerializer.class)
    public DateTime eventDate;
}

最後,我們可以將所有內容整合在一起並進行測試:

@Test
public void whenSerializingJodaTimeWithJackson_thenCorrect() 
  throws JsonProcessingException {
 
    DateTime date = new DateTime(2014, 12, 20, 2, 30);
    Event event = new Event("party", date);

    ObjectMapper mapper = new ObjectMapper();
    String result = mapper.writeValueAsString(event);
    assertThat(result, containsString("2014-12-20 02:30"));
}

9. 使用 Jackson 序列化 Java 8 的 Date

現在,讓我們看看如何使用 Jackson 序列化 Java 8 的 Date,在本例中,我們使用 LocalDateTime,並利用 jackson-datatype-jsr310 模塊:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.17.2</version>
</dependency>

然後,我們只需要註冊 JavaTimeModule (JSR310Module 已棄用),Jackson 將會處理剩下的工作:

@Test
public void whenSerializingJava8Date_thenCorrect()
  throws JsonProcessingException {
    LocalDateTime date = LocalDateTime.of(2014, 12, 20, 2, 30);

    ObjectMapper mapper = new ObjectMapper();
    mapper.registerModule(new JavaTimeModule());
    mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

    String result = mapper.writeValueAsString(date);
    assertThat(result, containsString("2014-12-20T02:30"));
}

10. 在不依賴任何額外庫的情況下序列化 Java 8 的 Date

如果不想引入額外的依賴,我們始終可以使用 自定義序列化器將 Java 8 的 DateTime 寫入 JSON:

public class CustomLocalDateTimeSerializer 
  extends StdSerializer<LocalDateTime> {

    private static DateTimeFormatter formatter = 
      DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");

    public CustomLocalDateTimeSerializer() {
        this(null);
    }
 
    public CustomLocalDateTimeSerializer(Class<LocalDateTime> t) {
        super(t);
    }
    
    @Override
    public void serialize(
      LocalDateTime value,
      JsonGenerator gen,
      SerializerProvider arg2)
      throws IOException, JsonProcessingException {
 
        gen.writeString(formatter.format(value));
    }
}

然後我們將使用序列化器處理我們的“eventDate”字段:

public class Event {
    public String name;

    @JsonSerialize(using = CustomLocalDateTimeSerializer.class)
    public LocalDateTime eventDate;
}

最後,我們將對其進行測試:

@Test
public void whenSerializingJava8DateWithCustomSerializer_thenCorrect()
  throws JsonProcessingException {
 
    LocalDateTime date = LocalDateTime.of(2014, 12, 20, 2, 30);
    Event event = new Event("party", date);

    ObjectMapper mapper = new ObjectMapper();
    String result = mapper.writeValueAsString(event);
    assertThat(result, containsString("2014-12-20 02:30"));
}

11. 反序列化 Date Date

現在讓我們看看如何使用 Jackson 反序列化一個 Date。 在下面的示例中,我們將反序列化一個包含日期的“Event”實例:

@Test
public void whenDeserializingDateWithJackson_thenCorrect()
  throws JsonProcessingException, IOException {
 
    String json = "{"name":"party","eventDate":"20-12-2014 02:30:00"}";

    SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
    ObjectMapper mapper = new ObjectMapper();
    mapper.setDateFormat(df);

    Event event = mapper.readerFor(Event.class).readValue(json);
    assertEquals("20-12-2014 02:30:00", df.format(event.eventDate));
}

12. 還原 Joda ZonedDateTime 時區信息

在默認配置下,Jackson 會將 Joda ZonedDateTime 的時區調整為本地上下文的時區。由於本地上下文的時區默認未設置,並且需要手動配置,因此 Jackson 會將時區調整為 GMT:

@Test
public void whenDeserialisingZonedDateTimeWithDefaults_thenNotCorrect()
  throws IOException {
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.findAndRegisterModules();
    objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    ZonedDateTime now = ZonedDateTime.now(ZoneId.of("Europe/Berlin"));
    String converted = objectMapper.writeValueAsString(now);

    ZonedDateTime restored = objectMapper.readValue(converted, ZonedDateTime.class);
    System.out.println("serialized: " + now);
    System.out.println("restored: " + restored);
    assertThat(now, is(restored));
}

測試用例將導致以下輸出:

serialized: 2017-08-14T13:52:22.071+02:00[Europe/Berlin]
restored: 2017-08-14T11:52:22.071Z[UTC]

幸運的是,針對這種奇怪的默認行為,有一個快速簡單的解決方案;我們只需要告訴 Jackson 不要調整時區。
可以通過在上述測試用例中添加以下代碼行來實現:

objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);

請注意,為了保持時區信息,我們還需要禁用日期自動序列化為時間戳的默認行為。

13. 自定義 日期 解序列化器

我們還可以使用自定義 日期 解序列化器。 我們將為屬性“eventDate”編寫自定義解序列化器:

public class CustomDateDeserializer extends StdDeserializer<Date> {

    private SimpleDateFormat formatter = 
      new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");

    public CustomDateDeserializer() {
        this(null);
    }

    public CustomDateDeserializer(Class<?> vc) {
        super(vc);
    }

    @Override
    public Date deserialize(JsonParser jsonparser, DeserializationContext context)
      throws IOException, JsonProcessingException {
        String date = jsonparser.getText();
        try {
            return formatter.parse(date);
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }
}

接下來,我們將使用它作為“eventDate”的反序列化器:

public class Event {
    public String name;

    @JsonDeserialize(using = CustomDateDeserializer.class)
    public Date eventDate;
}

最後,我們將對其進行測試:

@Test
public void whenDeserializingDateUsingCustomDeserializer_thenCorrect()
  throws JsonProcessingException, IOException {
 
    String json = "{"name":"party","eventDate":"20-12-2014 02:30:00"}";

    SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
    ObjectMapper mapper = new ObjectMapper();

    Event event = mapper.readerFor(Event.class).readValue(json);
    assertEquals("20-12-2014 02:30:00", df.format(event.eventDate));
}

14. 修復 InvalidDefinition異常

當創建 LocalDate 實例時,我們可能會遇到以下異常:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance
of `java.time.LocalDate`(no Creators, like default construct, exist): no String-argument
constructor/factory method to deserialize from String value ('2014-12-20') at [Source:
(String)"2014-12-20"; line: 1, column: 1]

此問題產生的原因是 JSON 本身沒有內置日期格式,因此它將日期表示為 String

String 類型的日期表示與內存中的 LocalDate 對象不同,因此我們需要一個外部反序列器來從 String 讀取該字段,以及一個序列化器來將日期渲染為 String 格式。

這些方法也適用於 LocalDateTime,唯一的區別在於使用相應的類來表示 LocalDateTime

14.1. Jackson 依賴

Jackson 允許我們通過多種方式解決這個問題。首先,我們需要確保 jsr310 依賴 存在於我們的 <em>pom.xml</em> 中:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.17.2</version>
</dependency>

14.2. 將序列化為單個日期對象

為了能夠處理 LocalDate,我們需要將 JavaTimeModule 註冊到我們的 ObjectMapper 中。

我們還需要在 ObjectMapper 中禁用 WRITE_DATES_AS_TIMESTAMPS 功能,以防止 Jackson 將時間數字添加到 JSON 輸出中:

@Test
public void whenSerializingJava8DateAndReadingValue_thenCorrect() throws IOException {
    String stringDate = "\"2014-12-20\"";

    ObjectMapper mapper = new ObjectMapper();
    mapper.registerModule(new JavaTimeModule());
    mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

    LocalDate result = mapper.readValue(stringDate, LocalDate.class);
    assertThat(result.toString(), containsString("2014-12-20"));
}

我們使用了 Jackson 對日期序列化和反序列化的原生支持。

14.3. POJO 中的標註

另一種處理該問題的辦法是,在實體級別使用 LocalDateDeserializerJsonFormat 註解:

public class EventWithLocalDate {

    @JsonDeserialize(using = LocalDateDeserializer.class)
    @JsonSerialize(using = LocalDateSerializer.class)
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy")
    public LocalDate eventDate;
}

@JsonDeserialize 註解用於指定自定義的反序列化器,以解映射 JSON 對象。類似地,@JsonSerialize 指示在解映射實體時應使用的自定義序列化器。

此外,@JsonFormat 註解允許我們指定用於序列化日期值的格式。因此,這個 POJO 可以用於讀取和寫入 JSON。

@Test
public void whenSerializingJava8DateAndReadingFromEntity_thenCorrect() throws IOException {
    String json = "{\"name\":\"party\",\"eventDate\":\"20-12-2014\"}";

    ObjectMapper mapper = new ObjectMapper();

    EventWithLocalDate result = mapper.readValue(json, EventWithLocalDate.class);
    assertThat(result.getEventDate().toString(), containsString("2014-12-20"));
}

雖然這種方法比使用 JavaTimeModule 的默認值需要更多的工作,但它更具可定製性。

15. 結論

在本文中,我們探討了 Date 文章中,Jackson 如何幫助將日期格式化為 JSON,並進行反序列化,利用我們可控的合理格式。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.