Java + Jackson Databind で JSON 日時文字列とオブジェクトを相互変換する

## 概要

- Java + Jackson Databind で JSON の日付・時刻を表す文字列と Java オブジェクトを相互変換するサンプルコードを示す
- 環境: Java 15 (AdoptOpenJDK 15.0.2+7) + Jackson Databind 2.12.1 + Jackson datatype: JSR310 2.12.1 + Gradle 6.8.2 + macOS Catalina

## サンプルコード

### ファイル一覧

```
├── build.gradle
└── src
    └── main
        └── java
            ├── Sample.java
            └── SampleData.java
```

### build.gradle

```groovy
plugins {
  id 'application'
  id 'java'
}

sourceCompatibility = JavaVersion.VERSION_15

application {
  mainClass = 'Sample'
}

repositories {
  mavenCentral()
}

dependencies {
  // Jackson Databind
  implementation 'com.fasterxml.jackson.core:jackson-databind:2.12.1'

  // java.time 系オブジェクトとの変換に必要
  // Add-on module to support JSR-310 (Java 8 Date & Time API) data types.
  implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.12.1'

  // Lombok
  compileOnly 'org.projectlombok:lombok:1.18.18'
  annotationProcessor 'org.projectlombok:lombok:1.18.18'
}
```


### src/main/java/Sample.java

Jackson Databind を使用して JSON 文字列と Java オブジェクトの相互変換をするクラス。

```java
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;

import java.io.IOException;
import java.io.StringWriter;

public class Sample {

  public static void main(String[] args) throws IOException {

    // 元データの JSON
    // 変換する Java 型によって使用するパースする際のフォーマッタ (SimpleDateFormat と DateTimeFormatter) が異なる
    // フォーマッタによって秒の小数部の精度 (ミリ秒・ナノ秒) が異なる
    String json = """
      {
        "utilDate"             : "2001-12-31 23:59:59.789",
        "utilDateIso8601"      : "2001-12-31T23:59:59.789+09:00",
        "utilCalendar"         : "2001-12-31 23:59:59.789",
        "utilCalendarIso8601"  : "2001-12-31T23:59:59.789+09:00",
        "sqlDate"              : "2001-12-31",
        "sqlTime"              :            "23:59:59",
        "sqlTimestamp"         : "2001-12-31 23:59:59.789",
        "sqlTimestampIso8601"  : "2001-12-31T23:59:59.789+09:00",
        "localDate"            : "2001-12-31",
        "localTime"            :            "23:59:59.123456789",
        "localDateTime"        : "2001-12-31 23:59:59.123456789",
        "localDateTimeIso8601" : "2001-12-31T23:59:59.123456789",
        "zonedDateTimeIso8601" : "2001-12-31T23:59:59.123456789+09:00"
      }""";
    System.out.println();
    System.out.println("元データの JSON");
    System.out.println(json);

    // JSON 文字列をオブジェクトに変換
    SampleData foo = new ObjectMapper()
      .registerModule(new JavaTimeModule()) // JSR-310 サポート
      .readValue(json, SampleData.class);
    System.out.println();
    System.out.println("JSON から変換したオブジェクト");
    System.out.println(foo.toString().replace(", ", ",\n")); // 読みやすく改行

    // オブジェクトを JSON 文字列に変換
    StringWriter writer = new StringWriter();
    new ObjectMapper()
      .registerModule(new JavaTimeModule()) // JSR-310 サポート
      .writerWithDefaultPrettyPrinter() // 読みやすく整形
      .writeValue(writer, foo);
    System.out.println();
    System.out.println("オブジェクトから変換した JSON");
    System.out.println(writer.toString());
  }
}
```

### src/main/java/SampleData.java

JSON マッピングするデータを表すクラス。

```java
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZonedDateTime;

@Data
public class SampleData {

  // java.util 系に適用する pattern は SimpleDateFormat 互換

  @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS", timezone = "Asia/Tokyo")
  private java.util.Date utilDate;

  @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", timezone = "Asia/Tokyo")
  private java.util.Date utilDateIso8601;

  @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS", timezone = "Asia/Tokyo")
  private java.util.Calendar utilCalendar;

  @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", timezone = "Asia/Tokyo")
  private java.util.Calendar utilCalendarIso8601;

  // java.sql 系に適用する pattern は SimpleDateFormat 互換

  @JsonFormat(pattern = "yyyy-MM-dd", timezone = "Asia/Tokyo")
  private java.sql.Date sqlDate;

  @JsonFormat(pattern = "HH:mm:ss", timezone = "Asia/Tokyo")
  private java.sql.Time sqlTime;

  @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS", timezone = "Asia/Tokyo")
  private java.sql.Timestamp sqlTimestamp;

  @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", timezone = "Asia/Tokyo")
  private java.sql.Timestamp sqlTimestampIso8601;

  // java.time 系に適用する pattern は DateTimeFormatter 互換

  @JsonFormat(pattern = "yyyy-MM-dd")
  private LocalDate localDate;

  @JsonFormat(pattern = "HH:mm:ss.nnnnnnnnn")
  private LocalTime localTime;

  @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.nnnnnnnnn")
  private LocalDateTime localDateTime;

  @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.nnnnnnnnn")
  private LocalDateTime localDateTimeIso8601;

  @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.nnnnnnnnnxxx", timezone = "Asia/Tokyo")
  private ZonedDateTime zonedDateTimeIso8601;
}
```

## サンプルコードの実行結果

```
$ gradle run

> Task :run

元データの JSON
{
  "utilDate"             : "2001-12-31 23:59:59.789",
  "utilDateIso8601"      : "2001-12-31T23:59:59.789+09:00",
  "utilCalendar"         : "2001-12-31 23:59:59.789",
  "utilCalendarIso8601"  : "2001-12-31T23:59:59.789+09:00",
  "sqlDate"              : "2001-12-31",
  "sqlTime"              :            "23:59:59",
  "sqlTimestamp"         : "2001-12-31 23:59:59.789",
  "sqlTimestampIso8601"  : "2001-12-31T23:59:59.789+09:00",
  "localDate"            : "2001-12-31",
  "localTime"            :            "23:59:59.123456789",
  "localDateTime"        : "2001-12-31 23:59:59.123456789",
  "localDateTimeIso8601" : "2001-12-31T23:59:59.123456789",
  "zonedDateTimeIso8601" : "2001-12-31T23:59:59.123456789+09:00"
}

JSON から変換したオブジェクト
SampleData(utilDate=Mon Dec 31 23:59:59 JST 2001,
utilDateIso8601=Mon Dec 31 23:59:59 JST 2001,
utilCalendar=java.util.GregorianCalendar[time=1009810799789,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="UTC",offset=0,dstSavings=0,useDaylight=false,transitions=0,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2001,MONTH=11,WEEK_OF_YEAR=1,WEEK_OF_MONTH=6,DAY_OF_MONTH=31,DAY_OF_YEAR=365,DAY_OF_WEEK=2,DAY_OF_WEEK_IN_MONTH=5,AM_PM=1,HOUR=2,HOUR_OF_DAY=14,MINUTE=59,SECOND=59,MILLISECOND=789,ZONE_OFFSET=0,DST_OFFSET=0],
utilCalendarIso8601=java.util.GregorianCalendar[time=1009810799789,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="UTC",offset=0,dstSavings=0,useDaylight=false,transitions=0,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2001,MONTH=11,WEEK_OF_YEAR=1,WEEK_OF_MONTH=6,DAY_OF_MONTH=31,DAY_OF_YEAR=365,DAY_OF_WEEK=2,DAY_OF_WEEK_IN_MONTH=5,AM_PM=1,HOUR=2,HOUR_OF_DAY=14,MINUTE=59,SECOND=59,MILLISECOND=789,ZONE_OFFSET=0,DST_OFFSET=0],
sqlDate=2001-12-31,
sqlTime=23:59:59,
sqlTimestamp=2001-12-31 23:59:59.789,
sqlTimestampIso8601=2001-12-31 23:59:59.789,
localDate=2001-12-31,
localTime=23:59:59.123456789,
localDateTime=2001-12-31T23:59:59.123456789,
localDateTimeIso8601=2001-12-31T23:59:59.123456789,
zonedDateTimeIso8601=2001-12-31T14:59:59.123456789Z[UTC])

オブジェクトから変換した JSON
{
  "utilDate" : "2001-12-31 23:59:59.789",
  "utilDateIso8601" : "2001-12-31T23:59:59.789+09:00",
  "utilCalendar" : "2001-12-31 23:59:59.789",
  "utilCalendarIso8601" : "2001-12-31T23:59:59.789+09:00",
  "sqlDate" : "2001-12-31",
  "sqlTime" : "23:59:59",
  "sqlTimestamp" : "2001-12-31 23:59:59.789",
  "sqlTimestampIso8601" : "2001-12-31T23:59:59.789+09:00",
  "localDate" : "2001-12-31",
  "localTime" : "23:59:59.123456789",
  "localDateTime" : "2001-12-31 23:59:59.123456789",
  "localDateTimeIso8601" : "2001-12-31T23:59:59.123456789",
  "zonedDateTimeIso8601" : "2001-12-31T23:59:59.123456789+09:00"
}

BUILD SUCCESSFUL in 1s
2 actionable tasks: 2 executed
```

## 参考資料

### Jackson Core Databind

- [GitHub \- FasterXML/jackson\-databind: General data\-binding package for Jackson \(2\.x\): works on streaming API \(core\) implementation\(s\)](https://github.com/FasterXML/jackson-databind)
- [JsonFormat \(Jackson\-annotations 2\.12\.1 API\)](https://javadoc.io/static/com.fasterxml.jackson.core/jackson-annotations/2.12.1/com/fasterxml/jackson/annotation/JsonFormat.html)
- [ObjectMapper \(jackson\-databind 2\.12\.1 API\)](https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-databind/2.12.1/com/fasterxml/jackson/databind/ObjectMapper.html)
- [DateDeserializers \(jackson\-databind 2\.12\.1 API\)](https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-databind/2.12.1/com/fasterxml/jackson/databind/deser/std/DateDeserializers.html)

### Jackson datatype: JSR310

Java 8 から導入された java.time パッケージの日時クラスに対応するためのパッケージ。

- [GitHub \- FasterXML/jackson\-modules\-java8: Set of support modules for Java 8 datatypes \(Optionals, date/time\) and features \(parameter names\)](https://github.com/FasterXML/jackson-modules-java8)
- [JavaTimeModule \(Jackson datatype: JSR310 2\.12\.1 API\)](https://javadoc.io/static/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.12.1/com/fasterxml/jackson/datatype/jsr310/JavaTimeModule.html)
- [JSR310DateTimeDeserializerBase \(Jackson datatype: JSR310 2\.12\.1 API\)](https://javadoc.io/doc/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.12.1/com/fasterxml/jackson/datatype/jsr310/deser/JSR310DateTimeDeserializerBase.html)
- [LocalDateTimeDeserializer \(Jackson datatype: JSR310 2\.12\.1 API\)](https://javadoc.io/doc/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.12.1/com/fasterxml/jackson/datatype/jsr310/deser/LocalDateTimeDeserializer.html)
- [jackson\-modules\-java8/JSR310DateTimeDeserializerBase\.java at jackson\-modules\-java8\-2\.12\.1 · FasterXML/jackson\-modules\-java8 · GitHub](https://github.com/FasterXML/jackson-modules-java8/blob/jackson-modules-java8-2.12.1/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310DateTimeDeserializerBase.java)

### 日時パターン文字列

SimpleDateFormat はミリ秒までの精度、DateTimeFormatter はナノ秒までの精度。

- [SimpleDateFormat \(Java SE 15 & JDK 15\)](https://docs.oracle.com/javase/jp/15/docs/api/java.base/java/text/SimpleDateFormat.html)
- [DateTimeFormatter \(Java SE 15 & JDK 15\)](https://docs.oracle.com/javase/jp/15/docs/api/java.base/java/time/format/DateTimeFormatter.html)
- [ISO 8601 \- Wikipedia](https://en.wikipedia.org/wiki/ISO_8601)

### Java 標準ライブラリの日時クラス

- [java\.util \(Java SE 15 & JDK 15\)](https://docs.oracle.com/javase/jp/15/docs/api/java.base/java/util/package-summary.html)
- [java\.sql \(Java SE 15 & JDK 15\)](https://docs.oracle.com/javase/jp/15/docs/api/java.sql/java/sql/package-summary.html)
- [java\.time \(Java SE 15 & JDK 15\)](https://docs.oracle.com/javase/jp/15/docs/api/java.base/java/time/package-summary.html)