1. 概要

このチュートリアルでは、Jacksonを使用して日付をシリアル化します。 まず、単純なjava.util。Date 、次にJoda-Time、最後にJava 8 DateTimeをシリアル化します。

2. 日付をタイムスタンプにシリアル化します

まず、単純なjava.util.DateをJacksonでシリアル化する方法を見てみましょう。

次の例では、 Dateフィールド「eventDate」を持つ「Event、」のインスタンスをシリアル化します。

@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);
}

ジャクソンはデフォルトで日付をタイムスタンプ形式(1970年1月1日UTCからのミリ秒数)にシリアル化することに注意することが重要です。

event」シリアル化の実際の出力は次のとおりです。

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

3. 日付をISO-8601にシリアル化します

この簡潔なタイムスタンプ形式へのシリアル化は最適ではありません。 代わりに、DateISO-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を使用して日付をフォーマットします

次に、 @JsonFormat アノテーションを見て、アプリケーション全体で、グローバルではなくの個々のクラスの日付形式を制御します。

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. カスタム日付シリアライザー

次に、出力を完全に制御するために、Datesのカスタムシリアライザーを利用します。

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. Joda-TimeWithJacksonをシリアル化する

日付は必ずしもjava.util.Date。のインスタンスであるとは限りません。実際、日付は他のクラスで表されることが多く、一般的なものはDateTimeです。 Joda-Timeライブラリからの実装。

ジャクソンでDateTimeをシリアル化する方法を見てみましょう。

jackson-datatype-joda モジュールを使用して、すぐに使用できるJoda-Timeをサポートします。

<dependency>
  <groupId>com.fasterxml.jackson.datatype</groupId>
  <artifactId>jackson-datatype-joda</artifactId>
  <version>2.9.7</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-TimeJackson依存関係が必要ない場合は、カスタムシリアライザー(前の例と同様)を使用して、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. Java8日付をJacksonでシリアル化

次に、Jackson を使用して、Java 8 DateTime、この例ではLocalDateTimeをシリアル化する方法を見てみましょう。 jackson-datatype-jsr310モジュールを利用できます。

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.9.7</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. Java8日付を余分な依存関係なしにシリアル化

追加の依存関係が必要ない場合は、いつでもカスタムシリアライザーを使用してJava8DateTimeを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. 日付を逆シリアル化します

次に、DateJacksonで逆シリアル化する方法を見てみましょう。 次の例では、日付を含む「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. タイムゾーンを保持したままJodaZonedDateTimeを逆シリアル化します

デフォルトの構成では、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]

幸い、この奇妙なデフォルトの動作には、すばやく簡単な修正があります。 ジャクソンにタイムゾーンを調整しないように指示する必要があります。

これは、上記のテストケースに以下のコード行を追加することで実行できます。

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. ジャクソン依存

ジャクソンは、いくつかの方法でこれを修正することを可能にします。 まず、jsr310依存関係pom.xmlにあることを確認する必要があります。

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

14.2. 単一の日付オブジェクトへのシリアル化

LocalDate を処理できるようにするには、JavaTimeModuleObjectMapperに登録する必要があります。

また、JacksonがJSON出力に時間の数字を追加しないように、ObjectMapperの機能WRITE_DATES_AS_TIMESTAMPSを無効にする必要があります。

@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での注釈

この問題に対処する別の方法は、エンティティレベルでLocalDateDeserializerおよびJsonFormatアノテーションを使用することです。

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への日付のマーシャリングとアンマーシャリングを支援するいくつかの方法を検討しました。

いつものように、サンプルコードはGitHubにあります。