これは、なにをしたくて書いたもの?
前のエントリーでJackson Databind 3を見てみましたが、今回はJakarta JSON Binding(通称JSON-B)を見ていこうと
思います。
Jackson Databind 3を試してみる - CLOVER🍀
Jakarta EEサーバーでは、Jakarta RESTful Web ServicesでJSONを扱う時によく使われているようなので。
実装はEclipse Yassonを使います。
Jakarta JSON Binding
Jakarta JSON Bindingの仕様はこちら。今回は3.0(Jakarta EE 10)を扱います。
Jakarta JSON Binding | Jakarta EE | The Eclipse Foundation
Jakarta JSON Binding 3.0 | Jakarta EE | The Eclipse Foundation
Jakarta JSON Binding(以降、JSON-B)はJavaオブジェクトとJSONドキュメント間のバインディングAPIです。要するに、
JavaオブジェクトとJSONの相互変換(シリアライズ、デシリアライズ)を可能にする仕様です。
Jakarta EE 10に含まれるJSON-Bのバージョンは3.0.0です。
JSON-Bの実装は以下の2つがあります。
- Eclipse Yasson
- Apache Johnzon Apache Johnzon – Apache Johnzon
通常、アプリケーションサーバーに同梱されているのはEclipse Yassonなので、今回はEclipse Yassonを使っていこうと
思います。
仕様書およびJavadocはこちらです。
Jakarta JSON Binding 3.0 | Jakarta EE | The Eclipse Foundation
jakarta.json.bind (JSON-B API 3.0.1 API)
小さくまとまっているので、見ても物量に圧倒されることはなさそうですね。
環境
今回の環境はこちら。
$ java --version openjdk 25.0.2 2026-01-20 OpenJDK Runtime Environment (build 25.0.2+10-Ubuntu-124.04) OpenJDK 64-Bit Server VM (build 25.0.2+10-Ubuntu-124.04, mixed mode, sharing) $ mvn --version Apache Maven 3.9.12 (848fbb4bf2d427b72bdb2471c22fced7ebd9a7a1) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 25.0.2, vendor: Ubuntu, runtime: /usr/lib/jvm/java-25-openjdk-amd64 Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "6.8.0-101-generic", arch: "amd64", family: "unix"
準備
Maven依存関係などはこちら。
<properties> <maven.compiler.release>25</maven.compiler.release> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> </properties> <dependencies> <dependency> <groupId>org.eclipse</groupId> <artifactId>yasson</artifactId> <version>3.0.4</version> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>6.0.3</version> <scope>test</scope> </dependency> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>3.27.7</version> <scope>test</scope> </dependency> </dependencies>
JSON-Bを使うには、yassonを依存関係に加えればOKです。
JSON-B(Eclipse Yasson)を使う
それでは、JSON-Bを使っていきましょう。
Eclipse YassonのREADME.mdには、JSON-Bの使い方が軽く書かれているだけです。
GitHub - eclipse-ee4j/yasson: Eclipse Yasson project
よって、基本的にはJSON-Bの仕様書を見ていくことになります。
ですが、JSON-Bの仕様書に書かれているのはデータ型のマッピングだったりカスタマイズに関する内容がほとんどです。
JSON-Bを使う時のエントリーポイントがわかりません。
それはEclipse YassonのREADME.mdに書かれています…。
Jsonb jsonb = JsonbBuilder.create(); String result = jsonb.toJson(someObject); // Create custom configuration JsonbConfig config = new JsonbConfig() .withNullValues(true) .withFormatting(true); // Create Jsonb with custom configuration Jsonb jsonb = JsonbBuilder.create(config); // Use it! String result = jsonb.toJson(someObject);
このページを見ないと、JsonbBuilderクラスの存在には気づきませんね…。
JsonbBuilder (JSON-B API 3.0.1 API)
確認はテストコードで行います。雛形はこちら。
src/test/java/org/littlewings/jsonb/JsonbTest.java
package org.littlewings.jsonb; import jakarta.json.bind.Jsonb; import jakarta.json.bind.JsonbBuilder; import jakarta.json.bind.JsonbConfig; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; class JsonbTest { // ここにテストを書く }
Mapを使ったJSONへのシリアライズ、JSONからのデシリアライズ。
@Test void useMap() throws Exception { try (Jsonb jsonb = JsonbBuilder.create()) { Map<String, Object> map = new LinkedHashMap<>(); // 順序固定のためLinkedHashMap map.put("string", "Hello World"); map.put("number", 5); map.put("datetime", LocalDateTime.of(2026, 2, 26, 17, 5, 0)); String json = jsonb.toJson(map); assertThat(json).isEqualTo(""" {"string":"Hello World","number":5,"datetime":"2026-02-26T17:05:00"}"""); Map<String, Object> expected = Map.of( "string", "Hello World", "number", new BigDecimal(5), // BigDecimal "datetime", "2026-02-26T17:05:00" // String ); assertThat(jsonb.fromJson(""" {"string":"Hello World","number":5,"datetime":"2026-02-26T17:05:00"}""", Map.class)) .isEqualTo(expected); } }
Mapにデシリアライズするとマッピングされる型がもとのコードとは変わってしまいますが…仕方ないですね。
デフォルトのデータ型のマッピングルールはこちらに書かれています。 Jakarta JSON Binding / Default Mapping
ちなみにJsonbのインスタンスはJsonbBuilder#createで取得できますが、これはクローズ対象です。
try (Jsonb jsonb = JsonbBuilder.create()) {
通常はクローズしなくても問題はなさそうですが、Jakarta Contexts and Dependency Injection(CDI)とインテグレーション
している場合はクローズしておいた方がよさそうです。
Pretty Printしてみましょう。JsonbConfigを使います。
@Test void prettyPrint() throws Exception { JsonbConfig config = new JsonbConfig().withFormatting(true); try (Jsonb jsonb = JsonbBuilder.create(config)) { // try (Jsonb jsonb = JsonbBuilder.newBuilder().withConfig(config).build()) { // または Map<String, Object> map = new LinkedHashMap<>(); // 順序固定のためLinkedHashMap map.put("string", "Hello World"); map.put("number", 5); map.put("datetime", LocalDateTime.of(2026, 2, 26, 17, 5, 0)); String json = jsonb.toJson(map); assertThat(json).isEqualTo(""" { "string": "Hello World", "number": 5, "datetime": "2026-02-26T17:05:00" }"""); Map<String, Object> expected = Map.of( "string", "Hello World", "number", new BigDecimal(5), // BigDecimal "datetime", "2026-02-26T17:05:00" // String ); assertThat(jsonb.fromJson(""" { "string": "Hello World", "number": 5, "datetime": "2026-02-26T17:05:00" }""", Map.class)) .isEqualTo(expected); } }
設定はJsonbConfigで行い、
JsonbConfig config = new JsonbConfig().withFormatting(true);
以下のどちらかの方法でJsonbのインスタンスを作成します。
try (Jsonb jsonb = JsonbBuilder.create(config)) { // または try (Jsonb jsonb = JsonbBuilder.newBuilder().withConfig(config).build()) {
Pretty Printではありませんが、JsonbConfigはマッピングルールのカスタマイズなどにも使うので、その説明箇所に出てきます。
Jakarta JSON Binding / Customizing Mapping
次は自分で作ったクラスにマッピングしてみましょう。
src/main/java/org/littlewings/jsonb/Person.java
package org.littlewings.jsonb; import java.util.ArrayList; import java.util.List; public class Person { private String firstName; private String lastName; private int age; private List<Person> friends = new ArrayList<>(); public Person() { } public Person(String firstName, String lastName, int age) { this.firstName = firstName; this.lastName = lastName; this.age = age; } public void addFriend(Person friend) { friends.add(friend); } // getter/setterは省略 }
こんな感じですね。
@Test void useBean() throws Exception { JsonbConfig config = new JsonbConfig().withFormatting(true); try (Jsonb jsonb = JsonbBuilder.create(config)) { Person katsuo = new Person("カツオ", "磯野", 11); katsuo.addFriend(new Person("弘", "中島", 10)); katsuo.addFriend(new Person("花子", "花沢", 11)); katsuo.addFriend(new Person("カオリ", "大空", 10)); assertThat(jsonb.toJson(katsuo)) .isEqualTo(""" { "age": 11, "firstName": "カツオ", "friends": [ { "age": 10, "firstName": "弘", "friends": [ ], "lastName": "中島" }, { "age": 11, "firstName": "花子", "friends": [ ], "lastName": "花沢" }, { "age": 10, "firstName": "カオリ", "friends": [ ], "lastName": "大空" } ], "lastName": "磯野" }"""); Person result = jsonb.fromJson(""" { "age": 11, "firstName": "カツオ", "friends": [ { "age": 10, "firstName": "弘", "friends": [ ], "lastName": "中島" }, { "age": 11, "firstName": "花子", "friends": [ ], "lastName": "花沢" }, { "age": 10, "firstName": "カオリ", "friends": [ ], "lastName": "大空" } ], "lastName": "磯野" }""", Person.class); assertThat(result.getFirstName()).isEqualTo("カツオ"); assertThat(result.getLastName()).isEqualTo("磯野"); assertThat(result.getAge()).isEqualTo(11); assertThat(result.getFriends()).hasSize(3); assertThat(result.getFriends().getFirst().getLastName()).isEqualTo("中島"); assertThat(result.getFriends().get(1).getLastName()).isEqualTo("花沢"); assertThat(result.getFriends().getLast().getLastName()).isEqualTo("大空"); } }
対象のクラスには、通常はデシリアライズ時に向けてデフォルトコンストラクターが必要になります。
デフォルトコンストラクターを使えない場合は、JsonbCreatorを使うことになるようです。
マッピングルールを変えてみましょう。@JsonbPropertyアノテーションを使って、フィールド名(プロパティー名)とは
別の項目名をマッピングします。
src/main/java/org/littlewings/jsonb/Person2.java
package org.littlewings.jsonb; import jakarta.json.bind.annotation.JsonbProperty; import java.util.ArrayList; import java.util.List; public class Person2 { @JsonbProperty("firstName") private String fName; @JsonbProperty("lastName") private String lName; @JsonbProperty("age") private int a; public Person2() { } public Person2(String fName, String lName, int a) { this.fName = fName; this.lName = lName; this.a = a; } // getter/setterは省略 }
確認。
@Test void useBean2() throws Exception { JsonbConfig config = new JsonbConfig().withFormatting(true); try (Jsonb jsonb = JsonbBuilder.create(config)) { Person2 katsuo = new Person2("カツオ", "磯野", 11); assertThat(jsonb.toJson(katsuo)) .isEqualTo(""" { "age": 11, "firstName": "カツオ", "lastName": "磯野" }"""); Person2 result = jsonb.fromJson(""" { "age": 11, "firstName": "カツオ", "lastName": "磯野" }""", Person2.class); assertThat(result.getfName()).isEqualTo("カツオ"); assertThat(result.getlName()).isEqualTo("磯野"); assertThat(result.getA()).isEqualTo(11); } }
最後。特にJSON-Bの仕様では言及されていませんが、Eclipse YassonではRecordsに対応しています。
src/main/java/org/littlewings/jsonb/PersonRecord.java
package org.littlewings.jsonb; import java.util.List; public record PersonRecord( String firstName, String lastName, int age, List<PersonRecord> friends ) { }
シリアライズ、デシリアライズともにOKでした。
@Test void useRecord() throws Exception { JsonbConfig config = new JsonbConfig().withFormatting(true); try (Jsonb jsonb = JsonbBuilder.create(config)) { List<PersonRecord> friends = List.of( new PersonRecord("弘", "中島", 10, Collections.emptyList()), new PersonRecord("花子", "花沢", 11, Collections.emptyList()), new PersonRecord("カオリ", "大空", 10, Collections.emptyList()) ); PersonRecord katsuo = new PersonRecord("カツオ", "磯野", 11, friends); assertThat(jsonb.toJson(katsuo)) .isEqualTo(""" { "age": 11, "firstName": "カツオ", "friends": [ { "age": 10, "firstName": "弘", "friends": [ ], "lastName": "中島" }, { "age": 11, "firstName": "花子", "friends": [ ], "lastName": "花沢" }, { "age": 10, "firstName": "カオリ", "friends": [ ], "lastName": "大空" } ], "lastName": "磯野" }"""); PersonRecord result = jsonb.fromJson(""" { "age": 11, "firstName": "カツオ", "friends": [ { "age": 10, "firstName": "弘", "friends": [ ], "lastName": "中島" }, { "age": 11, "firstName": "花子", "friends": [ ], "lastName": "花沢" }, { "age": 10, "firstName": "カオリ", "friends": [ ], "lastName": "大空" } ], "lastName": "磯野" }""", PersonRecord.class); assertThat(result).isEqualTo(katsuo); } }
こんなところでしょうか。
おわりに
Jakarta JSON Binding(JSON-B)をEclipse Yassonで試してみました。
簡単に終わると思いきや、最初にMapを対象にしたものだからデシリアライズに戻される型のルールをわかっていなくて
ちょっとハマったりしました…。
Jacksonを使わなくてもいいような場面では、こちらを使うのもよいのかなと思います。1番目にする機会があるのは、
Jakarta EEサーバーで使われている箇所だと思いますが。