3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JSON-B使い方メモ

Last updated at Posted at 2024-09-23

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

実装

build.gradle
plugins {
    id "java"
}

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

repositories {
    mavenCentral()
}

dependencies {
    implementation "org.eclipse:yasson:3.0.4"
}
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 省略
}
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);
        }
    }
}

実行結果

実行結果
{"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]}

説明

build.gradle
dependencies {
    implementation "org.eclipse:yasson:3.0.4"
}
  • Java SE 環境で使いたい場合は JSON-B を実装したライブラリを追加する必要がある
  • ここでは互換実装である Eclipse Yasson を指定している
JsonbMain.java
        Jsonb jsonb = JsonbBuilder.create();
        String json = jsonb.toJson(bean);
        ...
        MyBean deserialized = jsonb.fromJson(json, MyBean.class);
  • JSON-B は Jsonb を使ってシリアライズ・デシリアライズを行う
    • toJson で、 Java オブジェクトを JSON にシリアライズする
      • OutputStreamWriter に書き出すメソッドも用意されている
    • fromJson で、 JSON から Java オブジェクトにデシリアライズする
      • InputStreamReader から読み取るメソッドも用意されている
      • デシリアライズ対象のクラスにはデフォルトコンストラクタが存在しなければならない
    • 文字コードが指定されていない場合、デフォルトは UTF-8 で処理される
  • Jsonb のインスタンスは JsonbBuildercreate メソッドで取得できる
  • Jsonb インスタンスはスレッドセーフなので、1つだけ生成して使いまわすことができる

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

シリアライズ対象の仕様

実装

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

実行結果

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

説明

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

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

実装

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

実行結果

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

説明

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

インデントを入れる

実装

MyBean.java
package sandbox.jsonb;

public class MyBean {
    public String string = "string";
    public int number = 123;
}
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);
        }
    }
}

実行結果

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

説明

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

        try (Jsonb jsonb = JsonbBuilder.create(config)) {
  • JSON にインデントを入れて人間が見やすい形にフォーマットすることができる
  • JsonbConfig を用意して、 withFormattingtrue を設定する
  • この JsonbConfigJsonbBuildercreate に渡すことで、フォーマット時にインデントが入るようになる

入れ子のオブジェクトのマッピング

実装

Hoge.java
package sandbox.jsonb;

public class Hoge {
    public Fuga fuga = new Fuga();
    public String text = "Hoge";
    // toString省略
}
Fuga.java
package sandbox.jsonb;

public class Fuga {
    public String message = "Fuga";
    public int number = 12;
    // toString省略
}
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);
        }
    }
}

実行結果

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

説明

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

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

MyBean.java
package sandbox.jsonb;

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

    // toString 省略
}
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);
        }
    }
}

実行結果

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

説明

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

各 Java 型とのマッピング仕様

文字列型

MyBean.java
package sandbox.jsonb;

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

    // toString 省略
}
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);
        }
    }
}
実行結果
== Serialize ==
{
    "charValue": "c",
    "stringValue": "String"
}
== Deserialize ==
MyBean {
  stringValue = "String"
  charValue = c
}
  • String, char, Character は JSON の文字列とマッピングされる
    • 特に指定がなければ、文字コードは UTF-8 で処理される

数値型

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 省略
}
実行結果
== 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)

真偽値

MyBean.java
package sandbox.jsonb;

public class MyBean {
    public boolean booleanValue = true;

    // toString 省略
}
実行結果
== Serialize ==
{
    "booleanValue": true
}
== Deserialize ==
MyBean {
  booleanValue = true
}
  • boolean およびラッパー型は、 JSON の真偽値にマッピングされる
  • シリアライズ時は、ラッパー型の toString の仕様に従って変換される
  • デシリアライズ時は parseBoolean の仕様に従って変換される

BigInteger, BigDecimal

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 省略
}
実行結果
== Serialize ==
{
    "bigDecimal": 123.45,
    "bigInteger": 12345
}
== Deserialize ==
MyBean {
  bigInteger = 12345
  bigDecimal = 123.45
}
  • BigInteger, BigDecimal も、 JSON の数値としてマッピングされる
  • シリアライズ時は、各クラスの toString の仕様に従って変換される
  • デシリアライズ時は、各クラスの String を受け取るコンストラクタの仕様に従って変換される

URL, URI

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 省略
}
実行結果
== 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

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 省略
}
実行結果
== 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

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 省略
}
実行結果
== 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 または ISO_DATE_TIME に従ってフォーマットされる
  • 時刻の有無によってフォーマットは変化する

TimeZone

MyBean.java
package sandbox.jsonb;

import java.util.TimeZone;

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

    // toString 省略
}
実行結果
== Serialize ==
{
    "timeZone": "Asia/Tokyo"
}
== Deserialize ==
MyBean {
  timeZone = java.util.SimpleTimeZone[省略]
}
  • TimeZone は、 JSON の文字列とマッピングされる
  • シリアライズ時、仕様では TimeZone の Javadoc で説明されている 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 の日付・時刻等の型

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 省略
}
実行結果
== 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
java.time.LocalDate ISO_LOCAL_DATE
java.time.LocalTime ISO_LOCAL_TIME
java.time.LocalDateTime ISO_LOCAL_DATE_TIME
java.time.ZonedDateTime ISO_ZONED_DATE_TIME
java.time.OffsetDateTime ISO_OFFSET_DATE_TIME
java.time.OffsetTime ISO_OFFSET_TIME

ZoneId, ZoneOffset

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 省略
}
実行結果
== Serialize ==
{
    "zoneId": "Asia/Tokyo",
    "zoneOffset": "+09:00"
}
== Deserialize ==
MyBean {
  zoneId = Asia/Tokyo
  zoneOffset = +09:00
}
  • ZoneId, ZoneOffset は、 JSON の文字列にマッピングされる
  • シリアライズ時は正規化されたゾーンIDに変換される

Duration, Period

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 省略
}
実行結果
== Serialize ==
{
    "duration": "PT1H16M7S",
    "period": "P10Y5M3D"
}
== Deserialize ==
MyBean {
  duration = PT1H16M7S
  period = P10Y5M3D
}
  • Duration, Period は、 JSON の文字列とマッピングされる
  • シリアライズ時、 ISO 8601 の期間のフォーマットで変換される
  • ISO 8601 の期間のフォーマットは以下のような書式になっている
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

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

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 省略
}
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);
    }
}
実行結果
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 with a predictable iteration order.
(訳)
JSON オブジェクト値は、予測可能な反復順序で java.util.Map の実装に逆シリアル化されます。

https://jakarta.ee/specifications/jsonb/3.0/jakarta-jsonb-spec-3.0#untyped-mapping

コレクション

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 省略
}
実行結果
== 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]
}
  • ListMap などのコレクションは、 JSON のオブジェクトまたはリストにマッピングされる

配列

MyBean.java
package sandbox.jsonb;

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

    // toString 省略
}
実行結果
== Serialize ==
{
    "ints": [
        1,
        2,
        3
    ],
    "strings": [
        "hoge",
        "fuga",
        "piyo"
    ]
}
== Deserialize ==
MyBean {
  strings = [hoge, fuga, piyo]
  ints = [1, 2, 3]
}
  • 配列は JSON のリストにマッピングされる

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

実装

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 {
}
Hoge.java
package sandbox.jsonb;

public class Hoge implements MyInterface {
    public String message = "hoge";
    // toString省略
}
Fuga.java
package sandbox.jsonb;

public class Fuga implements MyInterface {
    public int number = 9;
    // toString省略
}
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);
        }
    }
}

実行結果

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

説明

  • ポリモーフィック(多態性)を用いたフィールドのマッピングを実現するには、 @JsonTypeInfo および @JsonSubtype アノテーションを利用する
MyInterface.java
@JsonbTypeInfo({
    @JsonbSubtype(alias = "Hoge", type = Hoge.class),
    @JsonbSubtype(alias = "Fuga", type = Fuga.class)
})
public interface MyInterface {
  • 親となる型に @JsonTypeInfo アノテーションを付ける
    • さらに value@JsonSubtype アノテーションを設定してサブタイプの情報を定義する
  • @JsonSubtype には aliastype を設定する
    • alias には、このサブタイプを識別する値を指定する
    • type には、サブタイプの Class オブジェクトを指定する
  • このアノテーションを設定すると、 JSON に型の情報が出力されるようになる
{
    "mi1": {
        "@type": "Hoge",
        "message": "hoge"
    },
    "mi2": {
        "@type": "Fuga",
        "number": 9
    }
}
  • @type プロパティに、サブタイプの型を識別する値が出力されている
    • @JsonSubtypealias に指定した値
  • デシリアライズ時は、この @type の情報と @JsonSubtype の情報を用いて型が決定される
  • @type というプロパティ名は @JsonTypeInfokey で変更できる
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変換結果
{
    "mi1": {
        "$TYPE": "Hoge",
        "message": "hoge"
    },
    "mi2": {
        "$TYPE": "Fuga",
        "number": 9
    }
}

マッピングの調整

マッピングの対象外にする

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 省略
}
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);
        }
    }
}
実行結果
== Serialize ==
{
    "ignoreDeserialize": "ignoreDeserialize",
    "normal": "normal"
}
== Deserialize ==
MyBean {
  normal = "aaa"
  ignore = "ignore"
  ignoreSerialize = "ccc"
  ignoreDeserialize = "ignoreDeserialize"
}

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

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

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 省略
}
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);
        }
    }
}
実行結果
== 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 プロパティ名とのマッピング戦略を変更する

実装

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

実行結果

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

説明

JsonbMain.java
        config.withPropertyNamingStrategy(
            PropertyNamingStrategy.LOWER_CASE_WITH_UNDERSCORES);
  • JsonbConfigwithPropertyNamingStrategy で、プロパティ名のマッピング戦略を変更できる
  • マッピング戦略は 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 も出力させる

実装

MyBean.java
package sandbox.jsonb;

import jakarta.json.bind.annotation.JsonbNillable;

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

    // toString 省略
}

実行結果

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

説明

  • @JsonbNillable を付けると、そのフィールドの値が null のときでも JSON にプロパティを出力させられるようになる
  • @JsonbNillable はクラスにもつけることができ、その場合は全フィールドが対象になる
  • 全てのシリアライズで設定を有効にしたい場合は、 JsonbConfig で設定する方法がある
全てのシリアライズでnullを出力させる
config.withNullValues(true);
  • withNullValuestrue を渡すことで、その JsonbConfig を使って生成した Jsonb によるシリアライズは、全て null 値のプロパティも出力するようになる

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

実装

MyBean.java
package sandbox.jsonb;

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

    // toString 省略
}
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 省略
}
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));
        }
    }
}

実行結果

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

説明

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

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

実装

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

実行結果

実行結果(見やすいように整形)
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.

説明

JsonbMain.java
        config.withCreatorParametersRequired(true);
  • JsonbConfigwithCreatorParametersRequiredtrue を設定すると、 @JsonbCreator の引数を必須扱いにできる
  • @JsonbProperty で指定されたプロパティが JSON に無い場合、デシリアライズ時にエラーとなる

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

実装

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 省略
}
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);
        }
    }
}

実行結果

実行結果
== 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
}

説明

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.Datejava.util.CalendarDateTimeFormatter の書式で指定できる
  • @JsonbDateFormat はクラスやパッケージにも指定可能で、その場合は配下に含まれる全てのプロパティが対象になる
    • クラスとフィールドの両方に @JsonbDateFormat が設定されている場合は、より狭い範囲の設定が優先される(この場合フィールドの設定が優先される)
  • 全ての処理で日付フォーマットを変更したい場合は、 JsonbConfig で設定する方法もある
JsonbConfigで日付フォーマットを指定する方法
config.withDateFormat("uuuu/MM/dd HH:mm:ss", Locale.getDefault());
  • JsonbConfigwithDateFormat で、デフォルトの日付フォーマットを設定できる
    • 第一引数は、 java.time.format.DateTimeFormatter で使用できるフォーマットを指定する
    • 第二引数は、 Locale を指定する
  • なお、ここでの設定は全ての日付・日時項目のフォーマットで使われることになるので、フォーマットで指定した日時項目を持たない日付・時刻型を変換対象のフィールドに使用していると実行時エラーとなるので注意
    • 例えばフォーマットを uuuu/MM/dd HH:mm:ss と指定した場合、年月日・時分秒の項目を持つことが前提となる
    • もし LocalTime 型のフィールドが変換対象にあると、実行時にエラーとなる

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

実装

MyBean.java
package sandbox.jsonb;

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

    // toString 省略
}
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);
        }
    }
}

実行結果

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

説明

JsonbMain.java
        config.withBinaryDataStrategy(BinaryDataStrategy.BASE_64);
  • JsonbConfigwithBinaryDataStrategyBinaryDataStrategy で定義されている定数を指定することで、 byte 配列のシリアライズ・デシリアライズの方法を変更できる
    • BASE_64
      • Baes64 でシリアライズ・デシリアライズが行われる
    • BASE_64_URL
      • URLセーフな Base64 でシリアライズ・デシリアライズが行われる
      • 詳しくは Base64 の Javadoc を参照
    • BYTE
      • byte の配列としてシリアライズ・デシリアライズが行われる
      • デフォルト

総称型のデシリアライズ

実装

MyBean.java
package sandbox.jsonb;

public class MyBean {
    public String message;
    // toString 省略
}
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);
        }
    }
}

実行結果

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

説明

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 は簡単にインスタンス化できるものではないので、取り方にやや癖がある
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 のインスタンスを取得する
  • 取得した TypefromJson または toJson の引数に渡すことで、総称型の変換ができるようになる
JsonbMain.java
            List<MyBean> list = jsonb.fromJson(..., listType);
            ...
            Map<String, MyBean> map = jsonb.fromJson(..., mapType);

参考

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?