🏔️

Jackson / ObjectMapperでOffsetDateTimeをDeserializeしたらUTCになる

2023/07/01に公開

はじめに

JacksonでJSONとJavaObjectとの相互変換を行うことは、よくある?ことだと思う。
SpringBootでREST-APIを利用する場合なども含む。

この記事は、
Javaの型として、java.time.LocalDateTimejava.time.OffsetDateTime を利用する場合の話。
これらの型を利用する場合は、 com.fasterxml.jackson.datatype:jackson-datatype-jsr310 などを利用する。そんな時に陥った罠があったのでメモを残しておく。

何が起こるのか

JacksonのObjectMapperでJSON文字列をObjectへ変換する際に、Offset情報を付加した値を読み込ませているのにも関わらず、
読み込んだ後のObjectではOffsetが無効になり、 UTC として扱われる。
以下のサンプルコードで示す。

実行結果はこちら

TimeZone: Asia/Tokyo
{
  "localDateTime" : "2023-07-01T16:39:43.4490064",
  "offsetDateTime" : "2023-07-01T05:39:43.4490064Z"
}

期待値は、 offsetDateTime2023-07-01T05:39:43.4490064+0900 と出力されること。
これはSpringBootでREST-APIを組んだ場合でも発生する。

なんでそうなるのか

Jacksonは 2.7 以降で、デフォルトのTimeZoneを UTC で扱うようになっている。
実際のソースコードは、 こちら のリンクから確認が可能。
抜粋したソースが以下。

/**
 * We will use a default TimeZone as the baseline.
 */
private static final TimeZone DEFAULT_TIMEZONE =
  //  TimeZone.getDefault()
  /* [databind#915] 05-Nov-2015, tatu: Changed to UTC, from earlier
   * baseline of GMT (up to 2.6)
   */
  TimeZone.getTimeZone("UTC");

JacksonのObjectMapperを生成する際に、デフォルトのTimeZoneを指定しない場合はこちらが利用される。
なお、コメントにも記載があるように 2.6 まではGMTであった模様...

対策

ObjectMapper の生成時にデフォルトのTimeZoneを設定することで回避が可能。
以下、デフォルトのTimeZoneを設定した場合と設定しない場合の比較を行うソースコードと結果となる。

実行結果はこちら

TimeZone: Asia/Tokyo
--- Case1 ---
{
  "localDateTime" : "2023-07-01T16:39:43.4490064",
  "offsetDateTime" : "2023-07-01T05:39:43.4490064Z"
}
--- Case2 ---
{
  "localDateTime" : "2023-07-01T16:39:43.4490064",
  "offsetDateTime" : "2023-07-01T14:39:43.4490064+09:00"
}

Discussion