その他のジャクソン注釈
1. 概要
この記事では、前の記事ジャクソン注釈ガイドではカバーされていなかったいくつかの追加の注釈について説明します。これらのうち7つについて説明します。
2. @JsonIdentityReference
@JsonIdentityReference は、完全なPOJOではなくオブジェクトIDとしてシリアル化されるオブジェクトへの参照のカスタマイズに使用されます。 @JsonIdentityInfo と連携して動作し、 @JsonIdentityReference がない場合とは異なり、すべてのシリアル化でオブジェクトIDの使用を強制します。 この2つの注釈は、オブジェクト間の循環依存を処理するときに最も役立ちます。 詳細については、 Jackson – BidirectionRelationshipの記事のセクション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
}
テストでは、 @JsonAppend アノテーションがない場合、[X45X]バージョンプロパティに追加しようとしているにもかかわらず、シリアル化出力に補足
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 :すべての文字は小文字で、名前要素間の区切り文字としてアンダースコアが付いています。例: snake_case。
- UPPER_CAMEL_CASE :最初の要素を含むすべての名前要素は、大文字で始まり、その後に小文字が続き、区切り文字はありません。例: UpperCamelCase。
この例では、スネークケース名を使用してプロパティをシリアル化する方法を示します。ここで、beanNameという名前のプロパティはbean_name。としてシリアル化されます。
Beanの定義が与えられた場合:
@JsonNaming(PropertyNamingStrategies.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ライブラリは、 JSONSchemaと呼ばれる別のモジュールを使用して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);
}
}
上記のコードでは、 @JsonPOJOBuilder を構成して、createBeanというビルドメソッドとconstructプレフィックスを使用してプロパティを照合しています。
@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としてシリアル化する必要があることを示すために使用されます。 そのポリモーフィックメタデータは、宣言されたスーパータイプではなく、シリアル化前と同じサブタイプのオブジェクトを再作成するために、逆シリアル化中に使用されます。
ジャクソンの継承の処理の詳細については、ジャクソンの継承のセクション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アノテーションについて詳しく説明しました。 これらの例とコードスニペットの実装は、GitHubプロジェクトにあります。