Jacksonのカスタムシリアライザーからデフォルトシリアライザーを呼び出す

1. 前書き

すべてのフィールドの正確な1対1表現を使用して完全なデータ構造をJSONにシリアル化することは、適切でない場合もあれば、単に必要なものではない場合もあります。 代わりに、*データの拡張ビューまたは簡易ビューを作成したい場合があります*。ここで、カスタムジャクソンシリアライザーが登場します。
ただし、カスタムシリアライザーの実装は、特にモデルオブジェクトに多数のフィールド、コレクション、またはネストされたオブジェクトがある場合、退屈な場合があります。 幸いなことに、https://www.baeldung.com/jackson [Jackson library]には、この仕事をもっと簡単にするいくつかの規定があります。
この短いチュートリアルでは、カスタムジャクソンシリアライザーを見て、*カスタムシリアライザー内のデフォルトシリアライザーにアクセスする方法*を示します。

2. サンプルデータモデル

ジャクソンのカスタマイズに飛び込む前に、シリアル化するサンプルの_Folder_クラスを見てみましょう。
public class Folder {
    private Long id;
    private String name;
    private String owner;
    private Date created;
    private Date modified;
    private Date lastAccess;
    private List<File> files = new ArrayList<>();

    // standard getters and setters
}
_File_クラスは、_Folder_クラス内で_List_として定義されています。
public class File {
    private Long id;
    private String name;

    // standard getters and setters
}

3. ジャクソンのカスタムシリアライザー

カスタムシリアライザーを使用する主な利点は、クラス構造を変更する必要がないことです。 さらに、*期待される動作をクラス自体から簡単に分離できます*。
したがって、_Folder_クラスのビューを縮小したいと考えてみましょう。
{
    "name": "Root Folder",
    "files": [
        {"id": 1, "name": "File 1"},
        {"id": 2, "name": "File 2"}
    ]
}
次のセクションで説明するように、ジャクソンで目的の出力を達成する方法はいくつかあります。

3.1. ブルートフォースアプローチ

最初に、Jacksonのデフォルトのシリアライザーを使用せずに、自分自身ですべての面倒な作業を行うカスタムシリアライザーを作成できます。
_Folder_クラスのカスタムシリアライザーを作成して、これを実現しましょう。
public class FolderJsonSerializer extends StdSerializer<Folder> {

    public FolderJsonSerializer() {
        super(Folder.class);
    }

    @Override
    public void serialize(Folder value, JsonGenerator gen, SerializerProvider provider)
      throws IOException {
        gen.writeStartObject();
        gen.writeStringField("name", value.getName());

        gen.writeArrayFieldStart("files");
        for (File file : value.getFiles()) {
            gen.writeStartObject();
            gen.writeNumberField("id", file.getId());
            gen.writeStringField("name", file.getName());
            gen.writeEndObject();
        }
        gen.writeEndArray();

        gen.writeEndObject();
    }
}
したがって、必要なフィールドのみを含む縮小ビューに_Folder_クラスをシリアル化できます。

3.2. 内部_ObjectMapper_を使用する

カスタムシリアライザーを使用すると、すべてのプロパティを詳細に変更できる柔軟性が得られますが、*ジャクソンのデフォルトシリアライザーを再利用する*ことで、作業を簡単にすることができます。
デフォルトのシリアライザーを使用する1つの方法は、内部の_ObjectMapper_クラスにアクセスすることです。
@Override
public void serialize(Folder value, JsonGenerator gen, SerializerProvider provider) throws IOException {
    gen.writeStartObject();
    gen.writeStringField("name", value.getName());

    ObjectMapper mapper = (ObjectMapper) gen.getCodec();
    gen.writeFieldName("files");
    String stringValue = mapper.writeValueAsString(value.getFiles());
    gen.writeRawValue(stringValue);

    gen.writeEndObject();
}
そのため、Jacksonは_File_オブジェクトの_List_をシリアル化することにより、重い作業を単純に処理し、出力は同じになります。

3.3. _SerializerProvider_の使用

デフォルトのシリアライザーを呼び出す別の方法は、_SerializerProvider._を使用することです。したがって、タイプ_File_のデフォルトのシリアライザーにプロセスを委任します。
ここで、_SerializerProvider_を使用してコードを少し簡略化しましょう。
@Override
public void serialize(Folder value, JsonGenerator gen, SerializerProvider provider) throws IOException {
    gen.writeStartObject();
    gen.writeStringField("name", value.getName());

    provider.defaultSerializeField("files", value.getFiles(), gen);

    gen.writeEndObject();
}
そして、前と同じように、同じ出力が得られます。

4. 考えられる再帰問題

ユースケースによっては、_Folder_の詳細を含めることにより、シリアル化されたデータを拡張する必要がある場合があります。 これは、*修正する機会がない*レガシーシステムまたは外部アプリケーションを統合するためのものである可能性があります。
シリアライザーを変更して、シリアル化されたデータの_details_フィールドを作成し、_Folder_クラスのすべてのフィールドを公開します。
@Override
public void serialize(Folder value, JsonGenerator gen, SerializerProvider provider) throws IOException {
    gen.writeStartObject();
    gen.writeStringField("name", value.getName());

    provider.defaultSerializeField("files", value.getFiles(), gen);

    // this line causes exception
    provider.defaultSerializeField("details", value, gen);

    gen.writeEndObject();
}
今回は、_StackOverflowError_例外が発生します。
*カスタムシリアライザーを定義すると、ジャクソンは_Folder_型に対して作成された元の_BeanSerializer_インスタンス*を内部でオーバーライドします。 その結果、_SerializerProvider_は、デフォルトのシリアライザーではなく、カスタマイズされたシリアライザーを毎回見つけます。これにより、無限ループが発生します。
それでは、この問題をどのように解決するのでしょうか? 次のセクションでは、このシナリオに使用できる1つのソリューションを紹介します。

5. _BeanSerializerModifier_の使用

考えられる回避策は、_BeanSerializerModifier_ *を使用して、_Folder_型のデフォルトのシリアライザーを保存します* Jacksonが内部でオーバーライドする前に*
シリアライザーを変更して、余分なフィールド_defaultSerializer_を追加しましょう。
private final JsonSerializer<Object> defaultSerializer;

public FolderJsonSerializer(JsonSerializer<Object> defaultSerializer) {
    super(Folder.class);
    this.defaultSerializer = defaultSerializer;
}
次に、_BeanSerializerModifier_の実装を作成して、デフォルトのシリアライザーを渡します。
public class FolderBeanSerializerModifier extends BeanSerializerModifier {

    @Override
    public JsonSerializer<?> modifySerializer(
      SerializationConfig config, BeanDescription beanDesc, JsonSerializer<?> serializer) {

        if (beanDesc.getBeanClass().equals(Folder.class)) {
            return new FolderJsonSerializer((JsonSerializer<Object>) serializer);
        }

        return serializer;
    }
}
次に、_BeanSerializerModifier_を機能させるモジュールとして登録する必要があります。
ObjectMapper mapper = new ObjectMapper();

SimpleModule module = new SimpleModule();
module.setSerializerModifier(new FolderBeanSerializerModifier());

mapper.registerModule(module);
次に、_details_フィールドに_defaultSerializer_を使用します。
@Override
public void serialize(Folder value, JsonGenerator gen, SerializerProvider provider) throws IOException {
    gen.writeStartObject();
    gen.writeStringField("name", value.getName());

    provider.defaultSerializeField("files", value.getFiles(), gen);

    gen.writeFieldName("details");
    defaultSerializer.serialize(value, gen, provider);

    gen.writeEndObject();
}
最後に、_details_から_files_フィールドを削除することもできます。これは、既にシリアル化されたデータに個別に書き込むためです。
したがって、_Folder_クラスの_files_フィールドを単に無視します。
@JsonIgnore
private List<File> files = new ArrayList<>();
最後に、問題は解決され、期待される出力も得られます。
{
    "name": "Root Folder",
    "files": [
        {"id": 1, "name": "File 1"},
        {"id": 2, "name": "File 2"}
    ],
    "details": {
        "id":1,
        "name": "Root Folder",
        "owner": "root",
        "created": 1565203657164,
        "modified": 1565203657164,
        "lastAccess": 1565203657164
    }
}

6. 結論

このチュートリアルでは、* Jackson Libraryのカスタムシリアライザー内でデフォルトのシリアライザーを呼び出す方法を学習しました。*
いつものように、このチュートリアルで使用されるすべてのコード例はhttps://github.com/eugenp/tutorials/tree/master/jackson-2[over on GitHub]で入手できます。