JSON-B使い方メモ

# JSON-B とは
- Java EE 8 で追加された仕様で、 JSON と Java オブジェクトの相互変換を行う API を定義している
- Jakarta EE での正式名称は Jakarta JSON Binding
- JAXB の JSON 版みたいなの
- B は Binding の B
- 読み方は、たぶん「じぇいそんびー」

# 環境
```
>gradle --version

------------------------------------------------------------
Gradle 8.8
------------------------------------------------------------

Build time:   2024-05-31 21:46:56 UTC
Revision:     4bd1b3d3fc3f31db5a26eecb416a165b8cc36082

Kotlin:       1.9.22
Groovy:       3.0.21
Ant:          Apache Ant(TM) version 1.10.13 compiled on January 4 2023
JVM:          21.0.3 (Eclipse Adoptium 21.0.3+9-LTS)
OS:           Windows 10 10.0 amd64
```

# Hello World
## 実装
```gradle:build.gradle
plugins {
    id "java"
}

compileJava.options.encoding = "UTF-8"
sourceCompatibility = 21
targetCompatibility = 21

repositories {
    mavenCentral()
}

dependencies {
    implementation "org.eclipse:yasson:3.0.4"
}
```

```java:MyBean.java
package sandbox.jsonb;

import java.util.List;

public class MyBean {
    public int number;
    public String string;
    public boolean bool;
    public List<String> stringList;

    // toString 省略
}
```

```java:JsonbMain.java
package sandbox.jsonb;

import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;

import java.util.List;

public class JsonbMain {

    public static void main(String[] args) throws Exception {
        MyBean bean = new MyBean();
        bean.number = 10;
        bean.bool = true;
        bean.string = "Hello JSON-B!!";
        bean.stringList = List.of("a", "b", "c");

        try (Jsonb jsonb = JsonbBuilder.create()) {
            String json = jsonb.toJson(bean);

            System.out.println(json);

            MyBean deserialized = jsonb.fromJson(json, MyBean.class);
            System.out.println(deserialized);
        }
    }
}
```

## 実行結果

```text:実行結果
{"bool":true,"number":10,"string":"Hello JSON-B!!","stringList":["a","b","c"]}
MyBean{number=10, string='Hello JSON-B!!', bool=true, stringList=[a, b, c]}
```

## 説明

```gradle:build.gradle
dependencies {
    implementation "org.eclipse:yasson:3.0.4"
}
```

- Java SE 環境で使いたい場合は JSON-B を実装したライブラリを追加する必要がある
- ここでは互換実装である [Eclipse Yasson](https://github.com/eclipse-ee4j/yasson) を指定している
    - [org.eclipse:yasson](https://mvnrepository.com/artifact/org.eclipse/yasson)

```java:JsonbMain.java
        Jsonb jsonb = JsonbBuilder.create();
        String json = jsonb.toJson(bean);
        ...
        MyBean deserialized = jsonb.fromJson(json, MyBean.class);
```

- JSON-B は `Jsonb` を使ってシリアライズ・デシリアライズを行う
    - `toJson` で、 Java オブジェクトを JSON にシリアライズする
        - `OutputStream` や `Writer` に書き出すメソッドも用意されている
    - `fromJson` で、 JSON から Java オブジェクトにデシリアライズする
        - `InputStream` や `Reader` から読み取るメソッドも用意されている
        - デシリアライズ対象のクラスにはデフォルトコンストラクタが存在しなければならない
    - 文字コードが指定されていない場合、デフォルトは UTF-8 で処理される
- `Jsonb` のインスタンスは `JsonbBuilder` の `create` メソッドで取得できる
- `Jsonb` インスタンスはスレッドセーフなので、１つだけ生成して使いまわすことができる

# シリアライズ・デシリアライズ対象の仕様
## シリアライズ対象の仕様
**実装**

```java:MyBean.java
package sandbox.jsonb;

public class MyBean {
    public static String staticMember = "staticMember";
    private String privateMember = "privateMember";
    protected String protectedMember = "protectedMember";
    String packagePrivateMember = "packagePrivateMember";
    public String publicMember = "publicMember";
    public String publicMember2 = "publicMember2";

    public String getPublicMember2() {
        return "getPublicMember2";
    }

    public String publicMember3() {
        return "publicMember3";
    }
}
```

```java:JsonbMain.java
package sandbox.jsonb;

import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;

public class JsonbMain {

    public static void main(String[] args) throws Exception {
        try (Jsonb jsonb = JsonbBuilder.create()) {
            String json = jsonb.toJson(new MyBean());
            System.out.println(json);
        }
    }
}
```

**実行結果**

```text:実行結果
{"publicMember":"publicMember","publicMember2":"getPublicMember2"}
```

**説明**

- Getter がある場合は Getter 優先で値が取得される
- Getter がなく、 `public` なフィールドのみある場合は、フィールドから直接値が取得される
- `public` でないフィールドはシリアライズの対象にならない
- `public` であっても、 `static` なフィールドはシリアライズの対象にならない

## デシリアライズ対象の仕様

**実装**

```java:MyBean.java
package sandbox.jsonb;

public class MyBean {
    public static String staticMember;
    private String privateMember;
    protected String protectedMember;
    String packagePrivateMember;
    public String publicMember;
    public String publicMember2;
    private String privateMember2;

    public void setPublicMember2(String publicMember2) {
        this.publicMember2 = "set(" + publicMember2 + ")";
    }

    public void privateMember2(String privateMember2) {
        this.privateMember2 = privateMember2;
    }

    @Override
    public String toString() {
        return "MyBean {\n" +
                "  staticMember='" + staticMember + '\'' +
                ",\n  privateMember='" + privateMember + '\'' +
                ",\n  protectedMember='" + protectedMember + '\'' +
                ",\n  packagePrivateMember='" + packagePrivateMember + '\'' +
                ",\n  publicMember='" + publicMember + '\'' +
                ",\n  publicMember2='" + publicMember2 + '\'' +
                ",\n  privateMember2='" + privateMember2 + '\'' +
                "\n}";
    }
}
```

```java:JsonbMain.java
package sandbox.jsonb;

import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;

public class JsonbMain {

    public static void main(String[] args) throws Exception {
        try (Jsonb jsonb = JsonbBuilder.create()) {
            MyBean bean = jsonb.fromJson("""
            {
                "staticMember": "staticMember",
                "privateMember": "privateMember",
                "protectedMember": "protectedMember",
                "packagePrivateMember": "packagePrivateMember",
                "publicMember": "publicMember",
                "publicMember2": "publicMember2",
                "privateMember2": "privateMember2"
            }
            """, MyBean.class);
            System.out.println(bean);
        }
    }
}
```

**実行結果**

```text:実行結果
MyBean {
  staticMember='null',
  privateMember='null',
  protectedMember='null',
  packagePrivateMember='null',
  publicMember='publicMember',
  publicMember2='set(publicMember2)',
  privateMember2='null'
}
```

**説明**

- Setter がある場合は Setter 優先で値が設定される
- Setter がなく、 `public` なフィールドのみがある場合は、フィールドに直接値が設定される
- `public` でないフィールドはデシリアライズの対象にならない
- `public` であっても、 `static` なフィールドはデシリアライズの対象にならない
- マッピング先のフィールドがない場合、エラーにはならず無視される

# インデントを入れる
**実装**

```java:MyBean.java
package sandbox.jsonb;

public class MyBean {
    public String string = "string";
    public int number = 123;
}
```

```java:JsonbMain.java
package sandbox.jsonb;

import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;
import jakarta.json.bind.JsonbConfig;

public class JsonbMain {

    public static void main(String[] args) throws Exception {
        JsonbConfig config = new JsonbConfig();
        config.withFormatting(true);

        try (Jsonb jsonb = JsonbBuilder.create(config)) {
            MyBean bean = new MyBean();

            String json = jsonb.toJson(bean);
            System.out.println(json);
        }
    }
}
```

**実行結果**

```text:実行結果
{
    "number": 123,
    "string": "string"
}
```

**説明**

```java:JsonbMain.java
        JsonbConfig config = new JsonbConfig();
        config.withFormatting(true);

        try (Jsonb jsonb = JsonbBuilder.create(config)) {
```

- JSON にインデントを入れて人間が見やすい形にフォーマットすることができる
- `JsonbConfig` を用意して、 `withFormatting` に `true` を設定する
- この `JsonbConfig` を `JsonbBuilder` の `create` に渡すことで、フォーマット時にインデントが入るようになる

# 入れ子のオブジェクトのマッピング
**実装**
```java:Hoge.java
package sandbox.jsonb;

public class Hoge {
    public Fuga fuga = new Fuga();
    public String text = "Hoge";
    // toString省略
}
```

```java:Fuga.java
package sandbox.jsonb;

public class Fuga {
    public String message = "Fuga";
    public int number = 12;
    // toString省略
}
```

```java:JonsbMain.java
package sandbox.jsonb;

import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;
import jakarta.json.bind.JsonbConfig;

public class JsonbMain {

    public static void main(String[] args) throws Exception {
        JsonbConfig config = new JsonbConfig();
        config.withFormatting(true);

        try (Jsonb jsonb = JsonbBuilder.create(config)) {
            Hoge bean = new Hoge();

            String json = jsonb.toJson(bean);
            System.out.println("== Serialize ==");
            System.out.println(json);

            Hoge deserialized = jsonb.fromJson(json, Hoge.class);
            System.out.println("== Deserialize ==");
            System.out.println(deserialized);
        }
    }
}
```

**実行結果**

```text:実行結果
== Serialize ==
{
    "fuga": {
        "message": "Fuga",
        "number": 12
    },
    "text": "Hoge"
}
== Deserialize ==
Hoge {
  fuga = Fuga {
    message = "Fuga"
    number = 12
  }
  text = "Hoge"
}
```

**説明**

- オブジェクトが入れ子になっていても問題なくマッピングできる

# エンコーディングを指定する

```java:MyBean.java
package sandbox.jsonb;

public class MyBean {
    public String message = "おはよう世界";

    // toString 省略
}
```

```java:JsonbMain.java
package sandbox.jsonb;

import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;
import jakarta.json.bind.JsonbConfig;

import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;

public class JsonbMain {

    public static void main(String[] args) throws Exception {
        JsonbConfig config = new JsonbConfig();
        config.withFormatting(true);
        config.withEncoding("Shift_JIS");

        Path jsonFile = Path.of("test.json");

        try (
            Jsonb jsonb = JsonbBuilder.create(config);
            OutputStream out = Files.newOutputStream(jsonFile);
            InputStream in = Files.newInputStream(jsonFile);
        ) {
            MyBean bean = new MyBean();

            jsonb.toJson(bean, out);
            MyBean deserialized = jsonb.fromJson(in, MyBean.class);
            System.out.println(deserialized);
        }
    }
}
```

**実行結果**

```json:test.json(Shift_JIS)
{
    "message": "おはよう世界"
}
```

```text:実行結果(Shift_JIS)
MyBean {
  message = "おはよう世界"
}
```

**説明**

```java:JsonbMain.java
        config.withEncoding("Shift_JIS");
```

- `JsonbConfig` の `withEncoding` でデフォルトの文字コードを指定できる
- 未指定の場合は UTF-8 になる
    - これは RFC 7159 (The JavaScript Object Notation (JSON) Data Interchange Format) に準拠しているため


# 各 Java 型とのマッピング仕様
## 文字列型
```java:MyBean.java
package sandbox.jsonb;

public class MyBean {
    public String stringValue = "String";
    public char charValue = 'c';

    // toString 省略
}
```

```java:JsonbMain.java
package sandbox.jsonb;

import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;
import jakarta.json.bind.JsonbConfig;

public class JsonbMain {

    public static void main(String[] args) throws Exception {
        JsonbConfig config = new JsonbConfig();
        config.withFormatting(true);

        try (Jsonb jsonb = JsonbBuilder.create(config)) {
            MyBean bean = new MyBean();

            String json = jsonb.toJson(bean);
            System.out.println("== Serialize ==");
            System.out.println(json);

            MyBean deserialized = jsonb.fromJson(json, MyBean.class);
            System.out.println("== Deserialize ==");
            System.out.println(deserialized);
        }
    }
}
```

```text:実行結果
== Serialize ==
{
    "charValue": "c",
    "stringValue": "String"
}
== Deserialize ==
MyBean {
  stringValue = "String"
  charValue = c
}
```

- `String`, `char`, `Character` は JSON の文字列とマッピングされる
    - 特に指定がなければ、文字コードは UTF-8 で処理される

## 数値型

```java:MyBean.java
package sandbox.jsonb;

public class MyBean {
    public byte byteValue = 1;
    public short shortValue = 2;
    public int intValue = 3;
    public long longValue = 4;
    public float floatValue = 5.1f;
    public double doubleValue = 6.2d;

    // toString 省略
}
```

```text:実行結果
== Serialize ==
{
    "byteValue": 1,
    "doubleValue": 6.2,
    "floatValue": 5.1,
    "intValue": 3,
    "longValue": 4,
    "shortValue": 2
}
== Deserialize ==
MyBean {
  byteValue = 1
  shortValue = 2
  intValue = 3
  longValue = 4
  floatValue = 5.1
  doubleValue = 6.2
}
```

- `byte`, `short`, `int`, `long`, `float`, `double` およびそのラッパー型は、 JSON の数値とマッピングされる
- シリアライズ時は、ラッパー型の `toString` メソッドの仕様に従って変換される
- デシリアライズ時は、 `parse$Type` メソッドの仕様に従って変換される(`byte` の場合は `parseByte`)

## 真偽値

```java:MyBean.java
package sandbox.jsonb;

public class MyBean {
    public boolean booleanValue = true;

    // toString 省略
}
```

```text:実行結果
== Serialize ==
{
    "booleanValue": true
}
== Deserialize ==
MyBean {
  booleanValue = true
}
```

- `boolean` およびラッパー型は、 JSON の真偽値にマッピングされる
- シリアライズ時は、ラッパー型の `toString` の仕様に従って変換される
- デシリアライズ時は `parseBoolean` の仕様に従って変換される

## BigInteger, BigDecimal

```java:MyBean.java
package sandbox.jsonb;

import java.math.BigDecimal;
import java.math.BigInteger;

public class MyBean {
    public BigInteger bigInteger = new BigInteger("12345");
    public BigDecimal bigDecimal = new BigDecimal("123.45");

    // toString 省略
}
```

```text:実行結果
== Serialize ==
{
    "bigDecimal": 123.45,
    "bigInteger": 12345
}
== Deserialize ==
MyBean {
  bigInteger = 12345
  bigDecimal = 123.45
}
```

- `BigInteger`, `BigDecimal` も、 JSON の数値としてマッピングされる
- シリアライズ時は、各クラスの `toString` の仕様に従って変換される
- デシリアライズ時は、各クラスの `String` を受け取るコンストラクタの仕様に従って変換される

## URL, URI

```java:MyBean.java
package sandbox.jsonb;

import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;

public class MyBean {
    public URI uri = URI.create("http://localhost:8080/hello");
    public URL url;

    public MyBean() throws MalformedURLException {
        url = uri.toURL();
    }

    // toString 省略
}
```

```text:実行結果
== Serialize ==
{
    "uri": "http://localhost:8080/hello",
    "url": "http://localhost:8080/hello"
}
== Deserialize ==
MyBean {
  uri = http://localhost:8080/hello
  url = http://localhost:8080/hello
}
```

- `URL`, `URI` は、 JSON の文字列とマッピングされる
- シリアライズ時は、各クラスの `toString` の仕様に従って変換される
- デシリアライズ時は、各クラスの `String` を受け取るコンストラクタの仕様に従って変換される

## Optional

```java:MyBean.java
package sandbox.jsonb;

import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;

public class MyBean {
    public Optional<String> optional = Optional.of("optional");
    public OptionalInt optionalInt = OptionalInt.of(10);
    public OptionalLong optionalLong = OptionalLong.of(20);
    public OptionalDouble optionalDouble = OptionalDouble.of(30.1);
    public Optional<String> empty = Optional.empty();

    // toString 省略
}
```

```text:実行結果
== Serialize ==
{
    "optional": "optional",
    "optionalDouble": 30.1,
    "optionalInt": 10,
    "optionalLong": 20
}
== Deserialize ==
MyBean {
  optional = Optional[optional]
  optionalInt = OptionalInt[10]
  optionalLong = OptionalLong[20]
  optionalDouble = OptionalDouble[30.1]
  empty = Optional.empty
}
```

- `Optional`, `OptionalInt`, `OptionalLong`, `OptionalDouble` は、その中身の値に対応する JSON の型にマッピングされる
- シリアライズ・デシリアライズ時は、中身の値の型に従った変換が行われる
    - 値が空の場合は `null` 扱いになる
- `null` をデシリアライズするときは空の `Optional` としてデシリアライズされる

## 日付
### Date, Calendar

```java:MyBean.java
package sandbox.jsonb;

import java.util.Calendar;
import java.util.Date;

public class MyBean {
    public Date date = new Date();
    public Calendar calendar = Calendar.getInstance();

    // toString 省略
}
```

```text:実行結果
== Serialize ==
{
    "calendar": "2024-09-23T16:22:30.979+09:00[Asia/Tokyo]",
    "date": "2024-09-23T07:22:30.972Z[UTC]"
}
== Deserialize ==
MyBean {
  date = Mon Sep 23 16:22:30 JST 2024
  calendar = java.util.GregorianCalendar[省略]
}
```

- `java.util.Date`, `Calendar` は、 JSON の文字列とマッピングされる
- シリアライズ時は DateTimeFormatter の [ISO_DATE](https://docs.oracle.com/javase/jp/21/docs/api/java.base/java/time/format/DateTimeFormatter.html#ISO_DATE) または [ISO_DATE_TIME](https://docs.oracle.com/javase/jp/21/docs/api/java.base/java/time/format/DateTimeFormatter.html#ISO_DATE_TIME) に従ってフォーマットされる
- 時刻の有無によってフォーマットは変化する

### TimeZone

```java:MyBean.java
package sandbox.jsonb;

import java.util.TimeZone;

public class MyBean {
    public TimeZone timeZone = TimeZone.getDefault();

    // toString 省略
}
```

```text:実行結果
== Serialize ==
{
    "timeZone": "Asia/Tokyo"
}
== Deserialize ==
MyBean {
  timeZone = java.util.SimpleTimeZone[省略]
}
```

- `TimeZone` は、 JSON の文字列とマッピングされる
- シリアライズ時、仕様では [TimeZone の Javadoc で説明されている NormalizedCustomID](https://docs.oracle.com/javase/jp/21/docs/api/java.base/java/util/TimeZone.html#NormalizedCustomID) に従って変換されると書かれているが、実際に動かすとタイムゾーンIDでも変換された（NormalizedCustomID でも変換可能）

> The serialization format of java.util.TimeZone and SimpleTimeZone is NormalizedCustomID as specified in java.util.TimeZone.
> （訳）
> java.util.TimeZone および SimpleTimeZone のシリアル化形式は、java.util.TimeZone で指定されている NormalizedCustomID です。
> 
> https://jakarta.ee/specifications/jsonb/3.0/jakarta-jsonb-spec-3.0#java-util-timezone-simpletimezone

### Date and Time API の日付・時刻等の型

```java:MyBean.java
package sandbox.jsonb;

import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.ZonedDateTime;

public class MyBean {
    public Instant instant = Instant.now();
    public LocalDate localDate = LocalDate.now();
    public LocalTime localTime = LocalTime.now();
    public LocalDateTime localDateTime = LocalDateTime.now();
    public ZonedDateTime zonedDateTime = ZonedDateTime.now();
    public OffsetDateTime offsetDateTime = OffsetDateTime.now();
    public OffsetTime offsetTime = OffsetTime.now();

    // toString 省略
}
```

```text:実行結果
== Serialize ==
{
    "instant": "2024-09-23T07:40:10.805080400Z",
    "localDate": "2024-09-23",
    "localDateTime": "2024-09-23T16:40:10.812083",
    "localTime": "16:40:10.812083",
    "offsetDateTime": "2024-09-23T16:40:10.8130825+09:00",
    "offsetTime": "16:40:10.8130825+09:00",
    "zonedDateTime": "2024-09-23T16:40:10.8130825+09:00[Asia/Tokyo]"
}
== Deserialize ==
MyBean {
  instant = 2024-09-23T07:40:10.805080400Z
  localDate = 2024-09-23
  localTime = 16:40:10.812083
  localDateTime = 2024-09-23T16:40:10.812083
  zonedDateTime = 2024-09-23T16:40:10.813082500+09:00[Asia/Tokyo]
  offsetDateTime = 2024-09-23T16:40:10.813082500+09:00
  offsetTime = 16:40:10.813082500+09:00
}
```

- `java.time.*` 以下の日時系のオブジェクトは、 JSON の文字列とマッピングされる
- シリアライズ時、それぞれの型は `DateTimeFormatter` で定義されている以下のフォーマットで変換される

|型|フォーマット|
|---|---|
|`java.time.Instant`       |[ISO_INSTANT](https://docs.oracle.com/javase/jp/21/docs/api/java.base/java/time/format/DateTimeFormatter.html#ISO_INSTANT)|
|`java.time.LocalDate`     |[ISO_LOCAL_DATE](https://docs.oracle.com/javase/jp/21/docs/api/java.base/java/time/format/DateTimeFormatter.html#ISO_LOCAL_DATE)|
|`java.time.LocalTime`     |[ISO_LOCAL_TIME](https://docs.oracle.com/javase/jp/21/docs/api/java.base/java/time/format/DateTimeFormatter.html#ISO_LOCAL_TIME)|
|`java.time.LocalDateTime` |[ISO_LOCAL_DATE_TIME](https://docs.oracle.com/javase/jp/21/docs/api/java.base/java/time/format/DateTimeFormatter.html#ISO_LOCAL_DATE_TIME)|
|`java.time.ZonedDateTime` |[ISO_ZONED_DATE_TIME](https://docs.oracle.com/javase/jp/21/docs/api/java.base/java/time/format/DateTimeFormatter.html#ISO_ZONED_DATE_TIME)|
|`java.time.OffsetDateTime`|[ISO_OFFSET_DATE_TIME](https://docs.oracle.com/javase/jp/21/docs/api/java.base/java/time/format/DateTimeFormatter.html#ISO_OFFSET_DATE_TIME)|
|`java.time.OffsetTime`    |[ISO_OFFSET_TIME](https://docs.oracle.com/javase/jp/21/docs/api/java.base/java/time/format/DateTimeFormatter.html#ISO_OFFSET_TIME)|

### ZoneId, ZoneOffset

```java:MyBean.java
package sandbox.jsonb;

import java.time.ZoneId;
import java.time.ZoneOffset;

public class MyBean {
    public ZoneId zoneId = ZoneId.systemDefault();
    public ZoneOffset zoneOffset= ZoneOffset.ofHours(9);

    // toString 省略
}
```

```text:実行結果
== Serialize ==
{
    "zoneId": "Asia/Tokyo",
    "zoneOffset": "+09:00"
}
== Deserialize ==
MyBean {
  zoneId = Asia/Tokyo
  zoneOffset = +09:00
}
```

- `ZoneId`, `ZoneOffset` は、 JSON の文字列にマッピングされる
- シリアライズ時は正規化されたゾーンIDに変換される

### Duration, Period

```java:MyBean.java
package sandbox.jsonb;

import java.time.Duration;
import java.time.Period;
import java.time.temporal.ChronoUnit;

public class MyBean {
    public Duration duration = Duration.of(4567, ChronoUnit.SECONDS);
    public Period period = Period.of(10, 5, 3);

    // toString 省略
}
```

```text:実行結果
== Serialize ==
{
    "duration": "PT1H16M7S",
    "period": "P10Y5M3D"
}
== Deserialize ==
MyBean {
  duration = PT1H16M7S
  period = P10Y5M3D
}
```

- `Duration`, `Period` は、 JSON の文字列とマッピングされる
- シリアライズ時、 ISO 8601 の期間のフォーマットで変換される
- ISO 8601 の期間のフォーマットは以下のような書式になっている

```text:ISO8601の期間のフォーマット
P[n]Y[n]M[n]DT[n]H[n]M[n]S
```

- `P` は「Period」を表す文字で、常に先頭につけられる
- `T` は時分秒の前につける識別子
- これ以外は、それぞれ時間の量(`n`)と単位(`Y`, `M`, ...)の組み合わせで表現されている

|単位|意味|
|---|---|
|`Y`|年|
|`M`|月|
|`D`|日|
|`H`|時|
|`M`|分|
|`S`|秒|

## 型を指定しない場合のマッピング

```java:MyBean.java
package sandbox.jsonb;

public class MyBean {
    public Object object;
    public Object array;
    public Object string;
    public Object number;
    public Object bool;
    public Object nullValue;

    // toString 省略
}
```

```java:JsonbMain.java
package sandbox.jsonb;

import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;

public class JsonbMain {

    public static void main(String[] args) throws Exception {
        try (Jsonb jsonb = JsonbBuilder.create()) {
            MyBean bean = jsonb.fromJson("""
            {
                "object": {
                    "hoge": "HOGE",
                    "fuga": "FUGA",
                    "piyo": "PIYO"
                },
                "array": [1, 2, 3],
                "string": "STRING",
                "number": 123,
                "bool": true,
                "nullValue": null
            }
            """, MyBean.class);

            print("object", bean.object);
            print("array", bean.array);
            print("string", bean.string);
            print("number", bean.number);
            print("bool", bean.bool);
            print("nullValue", bean.nullValue);
        }
    }

    private static void print(String name, Object value) {
        System.out.printf("%s: type=%s, value=%s%n",
            name,
            value == null ? "" : value.getClass().getName(),
            value);
    }
}
```

```text:実行結果
object: type=java.util.HashMap, value={hoge=HOGE, fuga=FUGA, piyo=PIYO}
array: type=java.util.ArrayList, value=[1, 2, 3]
string: type=java.lang.String, value=STRING
number: type=java.math.BigDecimal, value=123
bool: type=java.lang.Boolean, value=true
nullValue: type=, value=null
```

- Java 側の型を指定しない場合(`Object` で受けた場合)、各 JSON の値は以下のようにマッピングされる

|JSONでの型|Javaの型|
|---|---|
|`object` |`java.util.Map<String, Object>`|
|`array`  |`java.util.List<Object>`|
|`string` |`java.lang.String`|
|`number` |`java.math.BigDecimal`|
|`boolean`|`java.lang.Boolean`|
|`null`   |`null`|

- `object` については、仕様では「予測可能な反復順序 (predictable iteration order)」と書かれており、一見すると順序が保存される `LinkedHashMap` あたりが使われそうに思えたが、実際には `HashMap` になった（バグ？）

> JSON object values are deserialized into an implementation of java.util.Map<String, Object> with a predictable iteration order.
> (訳)
> JSON オブジェクト値は、予測可能な反復順序で java.util.Map<String, Object> の実装に逆シリアル化されます。
> 
> https://jakarta.ee/specifications/jsonb/3.0/jakarta-jsonb-spec-3.0#untyped-mapping

## コレクション

```java:MyBean.java
package sandbox.jsonb;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;

public class MyBean {
    public List<Integer> list = List.of(1, 2, 3);
    public Map<String, String> map
        = Map.of("hoge", "HOGE", "fuga", "FUGA", "piyo", "PIYO");
    public Set<Integer> set = Set.of(9, 8, 7);
    public Queue<String> queue = new LinkedList<>(
        List.of("a", "b", "c")
    );

    // toString 省略
}
```

```text:実行結果
== Serialize ==
{
    "list": [
        1,
        2,
        3
    ],
    "map": {
        "piyo": "PIYO",
        "fuga": "FUGA",
        "hoge": "HOGE"
    },
    "queue": [
        "a",
        "b",
        "c"
    ],
    "set": [
        9,
        8,
        7
    ]
}
== Deserialize ==
MyBean {
  list = [1, 2, 3]
  map = {hoge=HOGE, fuga=FUGA, piyo=PIYO}
  set = [7, 8, 9]
  queue = [a, b, c]
}
```

- `List` や `Map` などのコレクションは、 JSON のオブジェクトまたはリストにマッピングされる

## 配列

```java:MyBean.java
package sandbox.jsonb;

public class MyBean {
    public String[] strings = {"hoge", "fuga", "piyo"};
    public int[] ints = {1, 2, 3};

    // toString 省略
}
```

```text:実行結果
== Serialize ==
{
    "ints": [
        1,
        2,
        3
    ],
    "strings": [
        "hoge",
        "fuga",
        "piyo"
    ]
}
== Deserialize ==
MyBean {
  strings = [hoge, fuga, piyo]
  ints = [1, 2, 3]
}
```

- 配列は JSON のリストにマッピングされる


# ポリモーフィック型に対応させる

**実装**

```plantuml
interface MyInterface {}
class Hoge implements MyInterface {
  message: String
}
class Fuga implements MyInterface {
  number: int
}
```

```java:MyInterface.java
package sandbox.jsonb;

import jakarta.json.bind.annotation.JsonbSubtype;
import jakarta.json.bind.annotation.JsonbTypeInfo;

@JsonbTypeInfo({
    @JsonbSubtype(alias = "Hoge", type = Hoge.class),
    @JsonbSubtype(alias = "Fuga", type = Fuga.class)
})
public interface MyInterface {
}
```

```java:Hoge.java
package sandbox.jsonb;

public class Hoge implements MyInterface {
    public String message = "hoge";
    // toString省略
}
```

```java:Fuga.java
package sandbox.jsonb;

public class Fuga implements MyInterface {
    public int number = 9;
    // toString省略
}
```

```java:JsonbMain.java
package sandbox.jsonb;

import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;
import jakarta.json.bind.JsonbConfig;

public class JsonbMain {

    public static void main(String[] args) throws Exception {
        JsonbConfig config = new JsonbConfig();
        config.withFormatting(true);

        try (Jsonb jsonb = JsonbBuilder.create(config)) {
            MyBean bean = new MyBean();

            String json = jsonb.toJson(bean);
            System.out.println("== Serialize ==");
            System.out.println(json);

            MyBean deserialized = jsonb.fromJson(json, MyBean.class);
            System.out.println("== Deserialize ==");
            System.out.println(deserialized);
        }
    }
}
```

**実行結果**

```text:実行結果
== Serialize ==
{
    "mi1": {
        "@type": "Hoge",
        "message": "hoge"
    },
    "mi2": {
        "@type": "Fuga",
        "number": 9
    }
}
== Deserialize ==
MyBean {
  mi1 = Hoge {
    message = "hoge"
  }
  mi2 = Fuga {
    number = 9
  }
}
```

**説明**

- ポリモーフィック(多態性)を用いたフィールドのマッピングを実現するには、 `@JsonTypeInfo` および `@JsonSubtype` アノテーションを利用する

```java:MyInterface.java
@JsonbTypeInfo({
    @JsonbSubtype(alias = "Hoge", type = Hoge.class),
    @JsonbSubtype(alias = "Fuga", type = Fuga.class)
})
public interface MyInterface {
```

- 親となる型に `@JsonTypeInfo` アノテーションを付ける
    - さらに `value` に `@JsonSubtype` アノテーションを設定してサブタイプの情報を定義する
- `@JsonSubtype` には `alias` と `type` を設定する
    - `alias` には、このサブタイプを識別する値を指定する
    - `type` には、サブタイプの `Class` オブジェクトを指定する
- このアノテーションを設定すると、 JSON に型の情報が出力されるようになる

```json
{
    "mi1": {
        "@type": "Hoge",
        "message": "hoge"
    },
    "mi2": {
        "@type": "Fuga",
        "number": 9
    }
}
```

- `@type` プロパティに、サブタイプの型を識別する値が出力されている
    - `@JsonSubtype` の `alias` に指定した値
- デシリアライズ時は、この `@type` の情報と `@JsonSubtype` の情報を用いて型が決定される
- `@type` というプロパティ名は `@JsonTypeInfo` の `key` で変更できる

```java:MyInterface.java
package sandbox.jsonb;

import jakarta.json.bind.annotation.JsonbSubtype;
import jakarta.json.bind.annotation.JsonbTypeInfo;

@JsonbTypeInfo(
    key = "$TYPE",
    value = {
        @JsonbSubtype(alias = "Hoge", type = Hoge.class),
        @JsonbSubtype(alias = "Fuga", type = Fuga.class)
    }
)
public interface MyInterface {
}
```

```json:JSON変換結果
{
    "mi1": {
        "$TYPE": "Hoge",
        "message": "hoge"
    },
    "mi2": {
        "$TYPE": "Fuga",
        "number": 9
    }
}
```

# マッピングの調整
## マッピングの対象外にする
```java:MyBean.java
package sandbox.jsonb;

import jakarta.json.bind.annotation.JsonbTransient;

public class MyBean {
    public String normal = "normal";
    @JsonbTransient
    public String ignore = "ignore";
    private String ignoreSerialize = "ignoreSerialize";
    private String ignoreDeserialize = "ignoreDeserialize";

    @JsonbTransient
    public String getIgnoreSerialize() {
        return ignoreSerialize;
    }

    public void setIgnoreSerialize(String ignoreSerialize) {
        this.ignoreSerialize = ignoreSerialize;
    }

    public String getIgnoreDeserialize() {
        return ignoreDeserialize;
    }

    @JsonbTransient
    public void setIgnoreDeserialize(String ignoreDeserialize) {
        this.ignoreDeserialize = ignoreDeserialize;
    }

    // toString 省略
}
```

```java:JsonbMain.java
package sandbox.jsonb;

import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;
import jakarta.json.bind.JsonbConfig;

public class JsonbMain {

    public static void main(String[] args) throws Exception {
        JsonbConfig config = new JsonbConfig();
        config.withFormatting(true);

        try (Jsonb jsonb = JsonbBuilder.create(config)) {
            MyBean bean = new MyBean();

            String json = jsonb.toJson(bean);
            System.out.println("== Serialize ==");
            System.out.println(json);

            MyBean deserialized = jsonb.fromJson("""
            {
                "normal": "aaa",
                "ignore": "bbb",
                "ignoreSerialize": "ccc",
                "ignoreDeserialize": "ddd"
            }
            """, MyBean.class);
            System.out.println("== Deserialize ==");
            System.out.println(deserialized);
        }
    }
}
```

```text:実行結果
== Serialize ==
{
    "ignoreDeserialize": "ignoreDeserialize",
    "normal": "normal"
}
== Deserialize ==
MyBean {
  normal = "aaa"
  ignore = "ignore"
  ignoreSerialize = "ccc"
  ignoreDeserialize = "ignoreDeserialize"
}

```

- フィールドに `@JsonbTransient` を付けた場合、シリアライズとデシリアライズの両方が対象外になる
    - アクセサ経由のときもフィールドに `@JsonbTransient` を付けることが可能
- Getter に `@JsonbTransient` を付けた場合、シリアライズのみ対象外になる
- Setter に `@JsonbTransient` を付けた場合、デシリアライズのみ対象外になる

## JSON のプロパティ名を変更する

```java:MyBean.java
package sandbox.jsonb;

import jakarta.json.bind.annotation.JsonbProperty;

public class MyBean {
    public String normal = "normal";
    @JsonbProperty("textValue")
    public String text = "TEXT";

    private String onlySerialize = "onlySerialize";
    private String onlyDeserialize = "onlyDeserialize";

    @JsonbProperty("only_serialize")
    public String getOnlySerialize() {
        return onlySerialize;
    }

    public void setOnlySerialize(String onlySerialize) {
        this.onlySerialize = onlySerialize;
    }

    public String getOnlyDeserialize() {
        return onlyDeserialize;
    }

    @JsonbProperty("only_deserialize")
    public void setOnlyDeserialize(String onlyDeserialize) {
        this.onlyDeserialize = onlyDeserialize;
    }

    // toString 省略
}
```

```java:JsonbMain.java
package sandbox.jsonb;

import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;
import jakarta.json.bind.JsonbConfig;

public class JsonbMain {

    public static void main(String[] args) throws Exception {
        JsonbConfig config = new JsonbConfig();
        config.withFormatting(true);

        try (Jsonb jsonb = JsonbBuilder.create(config)) {
            MyBean bean = new MyBean();

            String json = jsonb.toJson(bean);
            System.out.println("== Serialize ==");
            System.out.println(json);

            MyBean deserialized = jsonb.fromJson("""
            {
                "normal": "aaa",
                "textValue": "bbb",
                "onlySerialize": "ccc",
                "only_deserialize": "ddd"
            }
            """, MyBean.class);
            System.out.println("== Deserialize ==");
            System.out.println(deserialized);
        }
    }
}
```

```text:実行結果
== Serialize ==
{
    "normal": "normal",
    "onlyDeserialize": "onlyDeserialize",
    "only_serialize": "onlySerialize",
    "textValue": "TEXT"
}
== Deserialize ==
MyBean {
  normal = "aaa"
  text = "bbb"
  onlySerialize = "ccc"
  onlyDeserialize = "ddd"
}
```

- `@JsonbProperty` を設定して `value` にプロパティ名を指定することで、 JSON のプロパティ名を任意の値に変更できる
- フィールドに `@JsonbProperty` を設定すると、シリアライズとデシリアライズの両方でプロパティ名を変更できる
- Getter に `@JsonbProperty` を設定すると、シリアライズ時のみプロパティ名を変更できる
- Setter に `@JsonbProperty` を設定すると、デシリアライズ時のみプロパティ名を変更できる

## JSON プロパティ名とのマッピング戦略を変更する

**実装**

```java:MyBean.java
package sandbox.jsonb;

public class MyBean {
    public String helloJsonBinding = "Hello JSON Binding";
    public int intValue = 1;

    // toString 省略
    @Override
    public String toString() {
        return ToStringUtil.toString(this);
    }
}
```

```java:JsonbMain.java
package sandbox.jsonb;

import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;
import jakarta.json.bind.JsonbConfig;
import jakarta.json.bind.config.PropertyNamingStrategy;

public class JsonbMain {

    public static void main(String[] args) throws Exception {
        JsonbConfig config = new JsonbConfig();
        config.withFormatting(true);
        config.withPropertyNamingStrategy(
            PropertyNamingStrategy.LOWER_CASE_WITH_UNDERSCORES);

        try (Jsonb jsonb = JsonbBuilder.create(config)) {
            MyBean bean = new MyBean();

            String json = jsonb.toJson(bean);
            System.out.println("== Serialize ==");
            System.out.println(json);

            MyBean deserialized = jsonb.fromJson(json, MyBean.class);
            System.out.println("== Deserialize ==");
            System.out.println(deserialized);
        }
    }
}
```

**実行結果**

```text:実行結果
== Serialize ==
{
    "hello_json_binding": "Hello JSON Binding",
    "int_value": 1
}
== Deserialize ==
MyBean {
  helloJsonBinding = "Hello JSON Binding"
  intValue = 1
}
```

**説明**

```java:JsonbMain.java
        config.withPropertyNamingStrategy(
            PropertyNamingStrategy.LOWER_CASE_WITH_UNDERSCORES);
```

- `JsonbConfig` の `withPropertyNamingStrategy` で、プロパティ名のマッピング戦略を変更できる
- マッピング戦略は [PropertyNamingStrategy](https://jakarta.ee/specifications/jsonb/3.0/apidocs/jakarta.json.bind/jakarta/json/bind/config/propertynamingstrategy) に定義された定数で指定できる

### プロパティ名のマッピング戦略

|定数|説明|例(Java <-> JSON)|
|---|---|---|
|`CASE_INSENSITIVE`            |※詳細後述|-|
|`IDENTITY`                    |変換無し|`propertyName` <-> `propertyName`|
|`LOWER_CASE_WITH_DASHES`      |小文字のダッシュ区切り|`propertyName` <-> `property-name`|
|`LOWER_CASE_WITH_UNDERSCORES` |小文字のアンスコ区切り|`propertyName` <-> `property_name`|
|`UPPER_CAMEL_CASE`            |大文字のキャメルケース|`propertyName` <-> `PropertyName`|
|`UPPER_CAMEL_CASE_WITH_SPACES`|大文字の空白区切りキャメルケース|`propertyName` <-> `Property Name`|

- `CASE_INSENSITIVE` だけちょっと特殊で、シリアライズ時は Java での名前がそのまま JSON のプロパティ名として利用される
- しかし、デシリアライズ時は大文字・小文字区別せずにマッピングが行われる
    - つまり、 JSON の `PropertyNAME` という名前のプロパティが、 Java 側の `propertyName` というフィールドにマッピングされる

## null も出力させる

**実装**

```java:MyBean.java
package sandbox.jsonb;

import jakarta.json.bind.annotation.JsonbNillable;

public class MyBean {
    public String text = "TEXT";
    @JsonbNillable
    public String nullValue = null;

    // toString 省略
}
```

**実行結果**

```json:シリアライズ結果
{
    "nullValue": null,
    "text": "TEXT"
}
```

**説明**

- `@JsonbNillable` を付けると、そのフィールドの値が `null` のときでも JSON にプロパティを出力させられるようになる
- `@JsonbNillable` はクラスにもつけることができ、その場合は全フィールドが対象になる
- 全てのシリアライズで設定を有効にしたい場合は、 `JsonbConfig` で設定する方法がある

```java:全てのシリアライズでnullを出力させる
config.withNullValues(true);
```

- `withNullValues` に `true` を渡すことで、その `JsonbConfig` を使って生成した `Jsonb` によるシリアライズは、全て `null` 値のプロパティも出力するようになる

## インスタンス生成の方法を指定する

**実装**

```java:MyBean.java
package sandbox.jsonb;

public class MyBean {
    public Hoge hoge = new Hoge("hoge");

    // toString 省略
}
```

```java:Hoge.java
package sandbox.jsonb;

import jakarta.json.bind.annotation.JsonbCreator;
import jakarta.json.bind.annotation.JsonbProperty;

public class Hoge {
    private final String text;

    @JsonbCreator
    public Hoge(@JsonbProperty("text") String text) {
        this.text = text;
    }

    public String getText() {
        return text;
    }

    // toString 省略
}
```

```java:JsonbMain.java
package sandbox.jsonb;

import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;
import jakarta.json.bind.JsonbConfig;

public class JsonbMain {

    public static void main(String[] args) throws Exception {
        JsonbConfig config = new JsonbConfig();
        config.withFormatting(true);

        try (Jsonb jsonb = JsonbBuilder.create(config)) {
            System.out.println(jsonb.fromJson("""
            {
                "hoge": {"text": "HOGE"}
            }
            """, MyBean.class));

            System.out.println("------------------------------");
            System.out.println(jsonb.fromJson("""
            {
                "hoge": {"unknown": "HOGE"}
            }
            """, MyBean.class));
        }
    }
}
```

**実行結果**

```text:実行結果
MyBean {
  hoge = Hoge {
    text = "HOGE"
  }
}
------------------------------
MyBean {
  hoge = Hoge {
    text = null
  }
}
```

**説明**

```java:Hoge.java
    @JsonbCreator
    public Hoge(@JsonbProperty("text") String text) {
        this.text = text;
    }
```

- デフォルトでは、デシリアライズ時にマッピング先となるクラスにはデフォルトコンストラクタが必須となる
    - なければエラー
- `@JsonbCreator` アノテーションを使用すると、デフォルトコンストラクタ以外の方法でインスタンスの生成方法を定義できる
- `@JsonbCreator` は、コンストラクタまたはファクトリメソッドに設定できる
- `@JsonbCreator` は、１つのクラスに１つだけ設定できる
    - 複数設定した場合はエラー
- `@JsonbCreator` が設定されたコンストラクタまたはファクトリメソッドは、デシリアライズ時にインスタンス生成で利用されるようになる
    - このとき、 JSON 上のどのプロパティを引数で受け取るかを `@JsonbProperty` アノテーションで設定できる
    - `value` に受け取るプロパティの名前を指定する
    - `@JsonbCreator` の引数はデフォルトではオプション扱いになる
        - JSON 上に `@JsonbProperty` で指定されたプロパティが無い場合は、 `null` などのデフォルト値が渡される（エラーにはならない）

### JsonbCreator の引数を必須扱いにする

**実装**

```java:JsonbMain.java
package sandbox.jsonb;

import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;
import jakarta.json.bind.JsonbConfig;

public class JsonbMain {

    public static void main(String[] args) throws Exception {
        JsonbConfig config = new JsonbConfig();
        config.withFormatting(true);
        config.withCreatorParametersRequired(true);

        try (Jsonb jsonb = JsonbBuilder.create(config)) {
            System.out.println(jsonb.fromJson("""
            {
                "hoge": {"text": "HOGE"}
            }
            """, MyBean.class));

            System.out.println("------------------------------");
            System.out.println(jsonb.fromJson("""
            {
                "hoge": {"unknown": "HOGE"}
            }
            """, MyBean.class));
        }
    }
}
```

**実行結果**

```text:実行結果(見やすいように整形)
MyBean {
  hoge = Hoge {
    text = "HOGE"
  }
}
------------------------------
Exception in thread "main" jakarta.json.bind.JsonbException:
    Unable to deserialize property 'hoge' because of:
      JsonbCreator parameter text is missing in json document.
```

**説明**

```java:JsonbMain.java
        config.withCreatorParametersRequired(true);
```

- `JsonbConfig` の `withCreatorParametersRequired` に `true` を設定すると、 `@JsonbCreator` の引数を必須扱いにできる
- `@JsonbProperty` で指定されたプロパティが JSON に無い場合、デシリアライズ時にエラーとなる

## 日付フォーマットを変更する

**実装**

```java:MyBean.java
package sandbox.jsonb;

import jakarta.json.bind.annotation.JsonbDateFormat;

import java.time.LocalDateTime;
import java.util.Date;

public class MyBean {
    @JsonbDateFormat("uuuu/MM/dd HH:mm:ss.SSS")
    public Date date = new Date();
    @JsonbDateFormat("uuuu/MM/dd HH:mm:ss.SSS")
    public LocalDateTime localDateTime = LocalDateTime.now();

    // toString 省略
}
```

```java:JsonbMain.java
package sandbox.jsonb;

import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;
import jakarta.json.bind.JsonbConfig;

public class JsonbMain {

    public static void main(String[] args) throws Exception {
        JsonbConfig config = new JsonbConfig();
        config.withFormatting(true);

        try (Jsonb jsonb = JsonbBuilder.create(config)) {
            MyBean bean = new MyBean();

            String json = jsonb.toJson(bean);
            System.out.println("== Serialize ==");
            System.out.println(json);

            MyBean deserialized = jsonb.fromJson(json, MyBean.class);
            System.out.println("== Deserialize ==");
            System.out.println(deserialized);
        }
    }
}
```

**実行結果**

```text:実行結果
== Serialize ==
{
    "date": "2024/09/23 14:20:49.598",
    "localDateTime": "2024/09/23 23:20:49.605"
}
== Deserialize ==
MyBean {
  date = Mon Sep 23 23:20:49 JST 2024
  localDateTime = 2024-09-23T23:20:49.605
}
```

**説明**

```java:MyBean.java
    @JsonbDateFormat("uuuu/MM/dd HH:mm:ss.SSS")
    public Date date = new Date();
    @JsonbDateFormat("uuuu/MM/dd HH:mm:ss.SSS")
    public LocalDateTime localDateTime = LocalDateTime.now();
```

- `@JsonbDateFormat` を設定することで、日付フォーマットを指定できる
- `value` には `java.time.format.DateTimeFormatter` で使用できるフォーマット書式を指定する
    - `java.util.Date` や `java.util.Calendar` も `DateTimeFormatter` の書式で指定できる
- `@JsonbDateFormat` はクラスやパッケージにも指定可能で、その場合は配下に含まれる全てのプロパティが対象になる
    - クラスとフィールドの両方に `@JsonbDateFormat` が設定されている場合は、より狭い範囲の設定が優先される（この場合フィールドの設定が優先される）
- 全ての処理で日付フォーマットを変更したい場合は、 `JsonbConfig` で設定する方法もある

```java:JsonbConfigで日付フォーマットを指定する方法
config.withDateFormat("uuuu/MM/dd HH:mm:ss", Locale.getDefault());
```

- `JsonbConfig` の `withDateFormat` で、デフォルトの日付フォーマットを設定できる
    - 第一引数は、 `java.time.format.DateTimeFormatter` で使用できるフォーマットを指定する
    - 第二引数は、 `Locale` を指定する
- なお、ここでの設定は全ての日付・日時項目のフォーマットで使われることになるので、フォーマットで指定した日時項目を持たない日付・時刻型を変換対象のフィールドに使用していると実行時エラーとなるので注意
    - 例えばフォーマットを `uuuu/MM/dd HH:mm:ss` と指定した場合、年月日・時分秒の項目を持つことが前提となる
    - もし `LocalTime` 型のフィールドが変換対象にあると、実行時にエラーとなる

## バイト配列を Base64 でエンコードする

**実装**

```java:MyBean.java
package sandbox.jsonb;

public class MyBean {
    public byte[] binary = {1, 2, 3, 4};

    // toString 省略
}
```

```java:JsonbMain.java
package sandbox.jsonb;

import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;
import jakarta.json.bind.JsonbConfig;
import jakarta.json.bind.config.BinaryDataStrategy;

public class JsonbMain {

    public static void main(String[] args) throws Exception {
        JsonbConfig config = new JsonbConfig();
        config.withFormatting(true);
        config.withBinaryDataStrategy(BinaryDataStrategy.BASE_64);

        try (Jsonb jsonb = JsonbBuilder.create(config)) {
            MyBean bean = new MyBean();

            String json = jsonb.toJson(bean);
            System.out.println("== Serialize ==");
            System.out.println(json);

            MyBean deserialized = jsonb.fromJson(json, MyBean.class);
            System.out.println("== Deserialize ==");
            System.out.println(deserialized);
        }
    }
}
```

**実行結果**

```text:実行結果
== Serialize ==
{
    "binary": "AQIDBA=="
}
== Deserialize ==
MyBean {
  binary = [1, 2, 3, 4]
}
```

**説明**

```java:JsonbMain.java
        config.withBinaryDataStrategy(BinaryDataStrategy.BASE_64);
```

- `JsonbConfig` の `withBinaryDataStrategy` に [BinaryDataStrategy](https://jakarta.ee/specifications/jsonb/3.0/apidocs/jakarta.json.bind/jakarta/json/bind/config/binarydatastrategy) で定義されている定数を指定することで、 byte 配列のシリアライズ・デシリアライズの方法を変更できる
    - `BASE_64`
        - Baes64 でシリアライズ・デシリアライズが行われる
    - `BASE_64_URL`
        - URLセーフな Base64 でシリアライズ・デシリアライズが行われる
        - 詳しくは [Base64 の Javadoc](https://docs.oracle.com/javase/jp/21/docs/api/java.base/java/util/Base64.html#url) を参照
    - `BYTE`
        - byte の配列としてシリアライズ・デシリアライズが行われる
        - デフォルト

# 総称型のデシリアライズ

**実装**

```java:MyBean.java
package sandbox.jsonb;

public class MyBean {
    public String message;
    // toString 省略
}
```

```java:JsonbMain.java
package sandbox.jsonb;

import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;
import jakarta.json.bind.JsonbConfig;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class JsonbMain {

    public static void main(String[] args) throws Exception {
        JsonbConfig config = new JsonbConfig();

        try (Jsonb jsonb = JsonbBuilder.create(config)) {
            Type listType =
                new ArrayList<MyBean>() {}.getClass().getGenericSuperclass();

            List<MyBean> list = jsonb.fromJson("""
            [
                {"message": "hoge"},
                {"message": "fuga"},
                {"message": "piyo"}
            ]
            """, listType);
            System.out.println(list);

            System.out.println("=================================");

            Type mapType =
                new HashMap<String, MyBean>() {}.getClass().getGenericSuperclass();

            Map<String, MyBean> map = jsonb.fromJson("""
            {
                "a": {"message": "A"},
                "b": {"message": "B"},
                "c": {"message": "C"}
            }
            """, mapType);
            System.out.println(map);
        }
    }
}
```

**実行結果**

```text:実行結果
[MyBean{message='hoge'}, MyBean{message='fuga'}, MyBean{message='piyo'}]
=================================
{a=MyBean{message='A'}, b=MyBean{message='B'}, c=MyBean{message='C'}}
```

**説明**

```java:JsonbMain.java
            List<MyBean> list = jsonb.fromJson("""
            [
                {"message": "hoge"},
                {"message": "fuga"},
                {"message": "piyo"}
            ]
            """, listType);
```

- `List<E>` や `Map<K, V>` のような総称型のオブジェクトに型を維持してデシリアライズする方法
- 単に `List<MyBean> list = jsonb.fromJson("...", List.class)` のようにすると、型引数の部分の情報が渡せないので、リストの各要素は `Map` にデシリアライズされてしまう
    - デシリアライズ先の型の情報がなく `Object` 扱いになるので、 `Map` にデシリアライズされる
    - 受け取る引数を `List<MyBean>` にしてもコンパイルは通るが、実体は `Map` なので実行時にキャストエラーが発生する
- 総称型の型引数の情報を渡すためには、 `java.lang.reflect.Type` 型のオブジェクトを渡す必要がある
    - `Type` は簡単にインスタンス化できるものではないので、取り方にやや癖がある

```java:JsonbMain.java
            Type listType =
                new ArrayList<MyBean>() {}.getClass().getGenericSuperclass();
            ...
            Type mapType =
                new HashMap<String, MyBean>() {}.getClass().getGenericSuperclass();
```

- 最終的なマッピング先にしたい総称型の具象型を継承した匿名クラスのインスタンスを生成する(`new ArrayList<MyBean>() {}`, `new HashMap<String, MyBean>() {}`)
- そして、生成したインスタンスの `getClass().getGenericSuperClass()` を呼ぶことで `Type` のインスタンスを取得する
- 取得した `Type` を `fromJson` または `toJson` の引数に渡すことで、総称型の変換ができるようになる

```java:JsonbMain.java
            List<MyBean> list = jsonb.fromJson(..., listType);
            ...
            Map<String, MyBean> map = jsonb.fromJson(..., mapType);
```



# 参考
- [Jakarta JSON Binding 3.0 | The Eclipse Foundation](https://jakarta.ee/specifications/jsonb/3.0/)
- [Jakarta JSON Binding](https://jakarta.ee/specifications/jsonb/3.0/jakarta-jsonb-spec-3.0)
- [GitHub - eclipse-ee4j/yasson: Eclipse Yasson project](https://github.com/eclipse-ee4j/yasson)
- [P2Y3DT6H8M！？　この暗号を解読するための ISO 8601 duration specification とは | 株式会社AMG Solution](https://amg-solution.jp/blog/9491)