1概要

この記事では、前の記事では扱われていなかったいくつかの追加の注釈を扱います。


2

@ JsonIdentityReference



@ JsonIdentityReference

は、完全なPOJOではなくオブジェクトIDとしてシリアル化されるオブジェクトへの参照のカスタマイズに使用されます。

@ JsonIdentityReference

が存在しない場合に初めて行われるのとは異なり、

@ JsonIdentityInfo

と連携して、すべてのシリアル化でオブジェクトIDの使用を強制します。この2つのアノテーションは、オブジェクト間の循環依存関係を処理するときに最も役立ちます。詳細については、リンク:/jackson-双方向の関係と無限再帰[Jackson – 双方向の関係]の記事のセクション4を参照してください。


@ JsonIdentityReference

の使い方を説明するために、このアノテーションを付けずに、または付けずに、2つの異なるBeanクラスを定義します。


@ JsonIdentityReference

がないBean

@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class BeanWithoutIdentityReference {
    private int id;
    private String name;

   //constructor, getters and setters
}


@ JsonIdentityReference

を使用しているBeanの場合、オブジェクトIDとして

id

プロパティを選択します。

@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
@JsonIdentityReference(alwaysAsId = true)
public class BeanWithIdentityReference {
    private int id;
    private String name;

   //constructor, getters and setters
}

最初のケース(

@ JsonIdentityReference

が存在しない場合)では、そのBeanはそのプロパティーに関する詳細を含めて直列化されています。

BeanWithoutIdentityReference bean
  = new BeanWithoutIdentityReference(1, "Bean Without Identity Reference Annotation");
String jsonString = mapper.writeValueAsString(bean);

上記のシリアライゼーションの出力:

{
    "id": 1,
    "name": "Bean Without Identity Reference Annotation"
}


@ JsonIdentityReference

を使用すると、Beanは代わりに単純なIDとしてシリアル化されます。

BeanWithIdentityReference bean
  = new BeanWithIdentityReference(1, "Bean With Identity Reference Annotation");
String jsonString = mapper.writeValueAsString(bean);
assertEquals("1", jsonString);


3

@ JsonAppend



@ JsonAppend

アノテーションは、オブジェクトがシリアル化されるときに通常のプロパティに加えて仮想プロパティをオブジェクトに追加するために使用されます。これは、クラス定義を変更するのではなく、補足情報を直接JSON文字列に追加する場合に必要です。たとえば、Beanの

version

メタデータを対応するJSONドキュメントに挿入するほうが、追加のプロパティを指定するよりも便利です。

次のように、

@ JsonAppend

がないBeanがあるとします。

public class BeanWithoutAppend {
    private int id;
    private String name;

   //constructor, getters and setters
}


ObjectWriter

オブジェクトに追加しようとしているにもかかわらず、

@ JsonAppend

アノテーションがない場合、シリアライゼーション出力に補足の

version

プロパティに関する情報が含まれていないことをテストで確認できます。

BeanWithoutAppend bean = new BeanWithoutAppend(2, "Bean Without Append Annotation");
ObjectWriter writer
  = mapper.writerFor(BeanWithoutAppend.class).withAttribute("version", "1.0");
String jsonString = writer.writeValueAsString(bean);

直列化出力:

{
    "id": 2,
    "name": "Bean Without Append Annotation"
}

それでは、@ @ JsonAppend__というアノテーションが付けられたBeanがあるとしましょう。

@JsonAppend(attrs = {
  @JsonAppend.Attr(value = "version")
})
public class BeanWithAppend {
    private int id;
    private String name;

   //constructor, getters and setters
}

前のものと同様のテストでは、

@ JsonAppend

アノテーションが適用されたときに、補足プロパティがシリアル化後に含まれることを確認します。

BeanWithAppend bean = new BeanWithAppend(2, "Bean With Append Annotation");
ObjectWriter writer
  = mapper.writerFor(BeanWithAppend.class).withAttribute("version", "1.0");
String jsonString = writer.writeValueAsString(bean);

そのシリアル化の出力は、

version

プロパティが追加されたことを示しています。

{
    "id": 2,
    "name": "Bean With Append Annotation",
    "version": "1.0"
}


4

@ JsonNaming



@ JsonNaming

アノテーションは、デフォルトをオーバーライドして、シリアル化におけるプロパティの命名方法を選択するために使用されます。

value

要素を使用して、カスタム戦略を含む任意の戦略を指定できます。

デフォルトに加えて、

LOWER

CAMEL

CASE

(例:


lowerCamelCase

)、Jacksonライブラリは便利のために4つの他の組み込みプロパティ命名戦略を提供しています。


  • KEBAB

    CASE__:名前要素はハイフンで区切られています。

ケバブケース。


  • LOWER

    CASE__:すべての文字は区切り文字なしの小文字です。

__低いです。


  • SNAKE

    CASE__:すべての文字は、アンダースコアを区切り文字として小文字

名前要素間

スネークケース


  • UPPER

    CAMEL

    CASE

    :最初のものも含め、すべてのname要素

大文字で始まり、その後に小文字が続き、区切り文字はありません

UpperCamelCase

この例では、

beanName

という名前のプロパティが

bean

name.__としてシリアル化されている、ヘビのケース名を使用してプロパティをシリアル化する方法を示します。

Bean定義を考えます。

@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class NamingBean {
    private int id;
    private String beanName;

   //constructor, getters and setters
}

以下のテストは、指定された命名規則が必要に応じて機能することを示しています。

NamingBean bean = new NamingBean(3, "Naming Bean");
String jsonString = mapper.writeValueAsString(bean);
assertThat(jsonString, containsString("bean__name"));


jsonString

変数には、以下のデータが含まれています。

{
    "id": 3,
    "bean__name": "Naming Bean"
}


5

@ JsonPropertyDescription


Jacksonライブラリはhttps://github.com/FasterXML/jackson-module-jsonSchema[JSON Schema]と呼ばれる別のモジュールの助けを借りて、Java型のためのJSONスキーマを作成することができます。このスキーマは、Javaオブジェクトをシリアル化するときに予期される出力を指定したい場合、または逆シリアル化の前にJSONドキュメントを検証したい場合に役立ちます。


@ JsonPropertyDescription

アノテーションを使用すると、

description

フィールドを指定して、作成したJSONスキーマに人間が読める形式の説明を追加できます。

このセクションでは、以下で宣言されているBeanを使用して

@ JsonPropertyDescription

の機能を説明します。

public class PropertyDescriptionBean {
    private int id;
    @JsonPropertyDescription("This is a description of the name property")
    private String name;

   //getters and setters
}


description

フィールドを追加してJSONスキーマを生成する方法を以下に示します。

SchemaFactoryWrapper wrapper = new SchemaFactoryWrapper();
mapper.acceptJsonFormatVisitor(PropertyDescriptionBean.class, wrapper);
JsonSchema jsonSchema = wrapper.finalSchema();
String jsonString = mapper.writeValueAsString(jsonSchema);
assertThat(jsonString, containsString("This is a description of the name property"));

ご覧のとおり、JSONスキーマの生成は成功しました。

{
    "type": "object",
    "id": "urn:jsonschema:com:baeldung:jackson:annotation:extra:PropertyDescriptionBean",
    "properties":
    {
        "name":
        {
            "type": "string",
            "description": "This is a description of the name property"
        },

        "id":
        {
            "type": "integer"
        }
    }
}


6.

@ JsonPOJOBuilder



@ JsonPOJOBuilder

アノテーションは、命名規則がデフォルトと異なる場合に、POJOを回復するためにJSON文書の逆シリアル化をカスタマイズするようにビルダークラスを設定するために使用されます。

次のJSON文字列を逆シリアル化する必要があるとします。

{
    "id": 5,
    "name": "POJO Builder Bean"
}

そのJSONソースは、

POJOBuilderBean

のインスタンスを作成するために使用されます。

@JsonDeserialize(builder = BeanBuilder.class)
public class POJOBuilderBean {
    private int identity;
    private String beanName;

   //constructor, getters and setters
}

Beanのプロパティの名前は、JSON文字列のフィールドの名前とは異なります。これが

@ JsonPOJOBuilder

が助けになるところです。


@ JsonPOJOBuilder

アノテーションは2つのプロパティを伴います:


  • buildMethodName

    :インスタンス化に使用された引数のないメソッドの名前

JSONフィールドをそのBeanのプロパティにバインドした後に予期されるBean

デフォルトの名前は

build

です。


  • withPrefix

    :一致の自動検出用の名前プレフィックス

JSONとBeanのプロパティデフォルトの接頭辞は

with

です。

この例では、以下の

POJOBuilderBean

で使用されている

BeanBuilder

クラスを使用します。

@JsonPOJOBuilder(buildMethodName = "createBean", withPrefix = "construct")
public class BeanBuilder {
    private int idValue;
    private String nameValue;

    public BeanBuilder constructId(int id) {
        idValue = id;
        return this;
    }

    public BeanBuilder constructName(String name) {
        nameValue = name;
        return this;
    }

    public POJOBuilderBean createBean() {
        return new POJOBuilderBean(idValue, nameValue);
    }
}

上記のコードでは、プロパティのマッチングに

createBean

というビルドメソッドと

construct

プレフィックスを使用するように

@ JsonPOJOBuilder

を設定しました。


@ JsonPOJOBuilder

のBeanへの適用は、次のように記述されテストされます。

String jsonString = "{\"id\":5,\"name\":\"POJO Builder Bean\"}";
POJOBuilderBean bean = mapper.readValue(jsonString, POJOBuilderBean.class);

assertEquals(5, bean.getIdentity());
assertEquals("POJO Builder Bean", bean.getBeanName());

結果は、プロパティの名前が一致していなくても、新しいデータオブジェクトがJSONソースから正常に再作成されたことを示しています。


7.

@ JsonTypeId



@ JsonTypeId

アノテーションは、注釈付きプロパティが、通常のプロパティとしてではなく、多態的な型情報を含めるときに型IDとしてシリアル化されるべきであることを示すために使用されます。その多型メタデータは、宣言されたスーパータイプではなく、直列化前と同じサブタイプのオブジェクトを再作成するために、逆シリアル化中に使用されます。

Jacksonの継承処理についての詳細は、リンクの/section 2を参照してください。

次のようなBeanクラス定義があるとしましょう。

public class TypeIdBean {
    private int id;
    @JsonTypeId
    private String name;

   //constructor, getters and setters
}

次のテストは、

@ JsonTypeId

が意図したとおりに機能することを検証します。

mapper.enableDefaultTyping(DefaultTyping.NON__FINAL);
TypeIdBean bean = new TypeIdBean(6, "Type Id Bean");
String jsonString = mapper.writeValueAsString(bean);

assertThat(jsonString, containsString("Type Id Bean"));

シリアル化プロセスの出力:

----[    "Type Id Bean",
    {
        "id": 6
    }]----


8

@ JsonTypeIdResolver



@ JsonTypeIdResolver

アノテーションは、シリアライゼーションおよびデシリアライゼーションにおけるカスタムタイプIDハンドラを表すために使用されます。そのハンドラは、JSONドキュメントに含まれるJavaの型と型IDの間の変換を担当します。

次のクラス階層を扱うときに型情報をJSON文字列に埋め込むとします。


AbstractBean

スーパークラス:

@JsonTypeInfo(
  use = JsonTypeInfo.Id.NAME,
  include = JsonTypeInfo.As.PROPERTY,
  property = "@type"
)
@JsonTypeIdResolver(BeanIdResolver.class)
public class AbstractBean {
    private int id;

    protected AbstractBean(int id) {
        this.id = id;
    }

   //no-arg constructor, getter and setter
}


FirstBean

サブクラス:

public class FirstBean extends AbstractBean {
    String firstName;

    public FirstBean(int id, String name) {
        super(id);
        setFirstName(name);
    }

   //no-arg constructor, getter and setter
}


LastBean

サブクラス:

public class LastBean extends AbstractBean {
    String lastName;

    public LastBean(int id, String name) {
        super(id);
        setLastName(name);
    }

   //no-arg constructor, getter and setter
}

これらのクラスのインスタンスは、

BeanContainer

オブジェクトを生成するために使用されます。

public class BeanContainer {
    private List<AbstractBean> beans;

   //getter and setter
}


AbstractBean

クラスに

@ JsonTypeIdResolver

というアノテーションが付けられていることがわかります。これは、カスタム

TypeIdResolver

を使用して、直列化にサブタイプ情報を含める方法と、その逆の方法でそのメタデータを使用する方法を決定します。

型情報の包含を処理するためのリゾルバクラスは次のとおりです。

public class BeanIdResolver extends TypeIdResolverBase {

    private JavaType superType;

    @Override
    public void init(JavaType baseType) {
        superType = baseType;
    }

    @Override
    public Id getMechanism() {
        return Id.NAME;
    }

    @Override
    public String idFromValue(Object obj) {
        return idFromValueAndType(obj, obj.getClass());
    }

    @Override
    public String idFromValueAndType(Object obj, Class<?> subType) {
        String typeId = null;
        switch (subType.getSimpleName()) {
        case "FirstBean":
            typeId = "bean1";
            break;
        case "LastBean":
            typeId = "bean2";
        }
        return typeId;
    }

    @Override
    public JavaType typeFromId(DatabindContext context, String id) {
        Class<?> subType = null;
        switch (id) {
        case "bean1":
            subType = FirstBean.class;
            break;
        case "bean2":
            subType = LastBean.class;
        }
        return context.constructSpecializedType(superType, subType);
    }
}

最も注目すべき2つのメソッドは

idFromValueAndType



typeFromId

です。前者はPOJOをシリアル化するときに型情報を含める方法を指示し、後者はそのメタデータを使用して再作成されたオブジェクトのサブタイプを決定します。

シリアライゼーションとデシリアライゼーションの両方がうまく機能することを確認するために、完全な進行を検証するためのテストを書きましょう。

まず、BeanコンテナとBeanクラスをインスタンス化してから、そのコンテナにBeanインスタンスを移入する必要があります。

FirstBean bean1 = new FirstBean(1, "Bean 1");
LastBean bean2 = new LastBean(2, "Bean 2");

List<AbstractBean> beans = new ArrayList<>();
beans.add(bean1);
beans.add(bean2);

BeanContainer serializedContainer = new BeanContainer();
serializedContainer.setBeans(beans);

次に、

BeanContainer

オブジェクトをシリアル化し、結果の文字列に型情報が含まれていることを確認します。

String jsonString = mapper.writeValueAsString(serializedContainer);
assertThat(jsonString, containsString("bean1"));
assertThat(jsonString, containsString("bean2"));

シリアル化の出力は以下のとおりです。

{
    "beans":
   [        {
            "@type": "bean1",
            "id": 1,
            "firstName": "Bean 1"
        },

        {
            "@type": "bean2",
            "id": 2,
            "lastName": "Bean 2"
        }
   ]}

そのJSON構造は、直列化前と同じサブタイプのオブジェクトを再作成するために使用されます。逆シリアル化の実装手順は次のとおりです。

BeanContainer deserializedContainer = mapper.readValue(jsonString, BeanContainer.class);
List<AbstractBean> beanList = deserializedContainer.getBeans();
assertThat(beanList.get(0), instanceOf(FirstBean.class));
assertThat(beanList.get(1), instanceOf(LastBean.class));


9結論

このチュートリアルでは、あまり一般的ではないいくつかのJackson注釈について詳しく説明しました。これらの例とコードスニペットの実装はhttps://github.com/eugenp/tutorials/tree/master/jackson[GitHubプロジェクト]にあります。