Jacksonで不変オブジェクトをデシリアライズする

1. 概要

このクイックチュートリアルでは、https://www.baeldung.com/jackson [Jackson] JSON処理ライブラリを使用して不変のJavaオブジェクトを逆シリアル化する2つの異なる方法を示します。

2. 不変オブジェクトを使用する理由

link:/java-immutable-object[immutable object]は、作成された瞬間から状態をそのまま保持するオブジェクトです*。 つまり、エンドユーザーがオブジェクトのどのメソッドを呼び出しても、オブジェクトは同じように動作します*。
不変性は一般にスレッドの安全性を保証するため、マルチスレッド環境で動作する必要があるシステムを設計するときに不変オブジェクトが役立ちます。
一方、不変オブジェクトは、外部ソースからの入力を処理する必要がある場合に役立ちます。 たとえば、ユーザー入力やストレージからのデータなどです。 その場合、受信したデータを保存し、偶発的または意図しない変更から保護することが重要になる場合があります*。
不変オブジェクトをデシリアライズする方法を見てみましょう。

3. パブリックコンストラクター

_Employee_クラス構造を考えてみましょう。 _id_と_name_の2つの必須フィールドがあるため、オブジェクトのフィールドセットに一致する引数セットを持つ* public all-argumentsコンストラクター*を定義します。
public class Employee {

    private final long id;
    private final String name;

    public Employee(long id, String name) {
        this.id = id;
        this.name = name;
    }

    // getters
}
これにより、作成時にすべてのオブジェクトのフィールドが初期化されます。 * fields宣言の最終修飾子は、今後値を変更することはできません。*このオブジェクトをデシリアライズ可能にするには、https://www.baeldung.com/jackson-annotations [annotationsを追加するだけです。 ]このコンストラクタへ:
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public Employee(@JsonProperty("id") long id, @JsonProperty("name") String name) {
    this.id = id;
    this.name = name;
}
追加したアノテーションを詳しく見てみましょう。
まず第一に、_ @ JsonCreator_はJacksonデシリアライザーに*指定されたコンストラクターを使用して逆シリアル化するように指示します*。
この注釈のパラメーターとして使用できるモードには、_PROPERTIES_と_DELEGATING_の2つがあります。
_PROPERTIES_は、すべての引数のコンストラクタを宣言する場合に最も適していますが、_DELEGATING_は単一の引数のコンストラクタに役立つ場合があります。
その後、*各コンストラクター引数*に注釈値としてそれぞれのプロパティの名前を示す_ @ JsonProperty_を注釈する必要があります。 *プロパティ名はすべて、シリアル化中に使用したものと一致する必要があるため、このステップでは非常に注意する必要があります。
_Employee_オブジェクトの逆シリアル化をカバーする簡単な単体テストを見てみましょう。
String json = "{\"name\":\"Frank\",\"id\":5000}";
Employee employee = new ObjectMapper().readValue(json, Employee.class);

assertEquals("Frank", employee.getName());
assertEquals(5000, employee.getId());

4. プライベートコンストラクターとビルダー

オブジェクトにオプションフィールドのセットが含まれることがあります。 オプションの_age_フィールドを持つ別のクラス構造_Person_を考えてみましょう。
public class Person {
    private final String name;
    private final Integer age;

    // getters
}
このようなフィールドが非常に多い場合、*パブリックコンストラクターの作成が面倒になる可能性があります*。 つまり、コンストラクターに多くの引数を宣言し、それぞれに_ @ JsonProperty_アノテーションを付ける必要があります。 その結果、多くの繰り返し宣言により、コードが肥大化し、読みにくくなります。
これは、古典的なlink:/creational-design-patterns[Builderパターン]が助けになる場合です。 デシリアライゼーションでその能力をどのように活用できるかを見てみましょう。 まず第一に、*プライベートなすべての引数のコンストラクタと_Builder_クラス*を宣言しましょう:
private Person(String name, Integer age) {
    this.name = name;
    this.age = age;
}

static class Builder {
    String name;
    Integer age;

    Builder withName(String name) {
        this.name = name;
        return this;
    }

    Builder withAge(Integer age) {
        this.age = age;
        return this;
    }

    public Person build() {
        return new Person(name, age);
    }
}
Jacksonのデシリアライザーでこの_Builder_を使用するには、コードに2つの注釈を追加するだけです。 まず、クラスを_ @ JsonDeserialize_アノテーションでマークし、_builder_パラメーターに*完全修飾ドメイン名のビルダークラス*を渡す必要があります。
その後、ビルダークラス自体に_ @ JsonPOJOBuilder_として注釈を付ける必要があります。
@JsonDeserialize(builder = Person.Builder.class)
public class Person {
    //...

    @JsonPOJOBuilder
    static class Builder {
        //...
    }
}
ビルド中に使用されるメソッドの名前をカスタマイズできることに注意してください。
パラメーター_buildMethodName_のデフォルトは「_build」_であり、ビルダーが新しいオブジェクトを生成する準備ができたときに呼び出す*メソッドの名前を表します*。
別のパラメーター_withPrefix_は、プロパティの設定を担当するビルダーメソッドに追加する* prefixを表します。 このパラメーターのデフォルト値は、_with _です。 そのため、この例ではこれらのパラメーターを指定しませんでした。
_Person_オブジェクトの逆シリアル化をカバーする簡単な単体テストを見てみましょう。
String json = "{\"name\":\"Frank\",\"age\":50}";
Person person = new ObjectMapper().readValue(json, Person.class);

assertEquals("Frank", person.getName());
assertEquals(50, person.getAge().intValue());

5. 結論

この短い記事では、Jacksonライブラリを使用して不変オブジェクトを逆シリアル化する方法を説明しました。
この記事に関連するすべてのコードは、https://github.com/eugenp/tutorials/tree/master/jackson [Github]で見つけることができます。