TypeAdapterによるJSON変換(GSON)を複数クラスに適用させる

本ページの目的

前回、JsonSerializer(TypeAdapter)を用いて、GSONでデータクラス(バリューオブジェクト)のListやMapをJSONに変換する際、フィールド名などを出さないようにする方法を確認しました。

今回は複数のデータクラスに対するJsonSerializerの実装を共通にする方法について見ていきます。複数のクラスに対して共通の動きを定義するということで今回はインターフェースを用いて実現できるかを見ていきます。

前回の記事

準備

まずは準備としてデータクラスとJsonSerializerを用意しておきます。

JsonSerializer

JsonSerializerとして以下のようなものを用意しました。

public interface IValueObject {
    public String getValue();

    public static final JsonSerializer<IValueObject> JSON_SERIALIZER = new JsonSerializer<IValueObject>() {
        @Override
        public JsonElement serialize(IValueObject src, Type typeOfSrc, JsonSerializationContext context) {
            return context.serialize(src.getValue());
        }
    };
}

インターフェースで値を取得するメソッドを定義し、JsonSerializerではそのメソッドを用いる形にしています。

データクラス(バリューオブジェクト)

データクラスとして以下の2つを用意しています。

public class ValueObjectA implements IValueObject {

    private String value;

    @Override
    public String getValue() {
        return this.value;
    }
}
public class ValueObjectB implements IValueObject {

    private String value;

    @Override
    public String getValue() {
        return this.value;
    }
}

それぞれ、先ほどのインターフェースを実装しています。

成功例

準備したJsonSerializerとデータクラスを用いて動きを確認していきます。

    @Test
    public void multiTypeTest() {

        List<IValueObject> list = Arrays.asList(
                new ValueObjectA("vo1"), new ValueObjectB("vo2"), new ValueObjectA("vo3")
        );

        Gson gson = new GsonBuilder()
                .registerTypeAdapter(ValueObjectA.class, IValueObject.JSON_SERIALIZER)
                .registerTypeAdapter(ValueObjectB.class, IValueObject.JSON_SERIALIZER)
                .create();

        String json = gson.toJson(list);
        System.out.println(json);
    }

上記は、GsonBuilderのregisterTypeAdapterを複数呼び出しており、第一引数のタイプ(クラス)の指定で、データクラスのタイプ(クラス)をそれぞれ指定しています。第二引数のTypeAdapter(JsonSerializer)は同じものを指定しています。

これを実行すると以下のようになります。

["vo1","vo2","vo3"]

複数クラスに対して、共通のJsonSerializerを用いることができました。

エラーケース

ここでは補足として期待通りの動きをしない場合の動きも見てみます。

第一引数で全てのクラスを指定しない

以下はregisterTypeAdapterの実行を全てのクラスに対して指定しなかったケースです。

    @Test
    public void multiTypeTest() {
        List<IValueObject> list = Arrays.asList(
                new ValueObjectA("vo1"), new ValueObjectB("vo2"), new ValueObjectA("vo3")
        );

        Gson gson = new GsonBuilder()
                .registerTypeAdapter(ValueObjectA.class, IValueObject.JSON_SERIALIZER)
                .create();

        String json = gson.toJson(list);
        System.out.println(json);
    }

上記を実行すると以下のようになります。

["vo1",{"value":"vo2"},"vo3"]

クラスAの方は期待通りフィールド名が出力されない形となっていますが、registerTypeAdapterを実行していないクラスBの方はフィールド名(value)が表示されてしまっています。

第一引数で実装クラスではなくインターフェースを指定する

以下はregisterTypeAdapterで指定するタイプ(クラス)を実装クラスではなくインターフェースにしたケースです。

    @Test
    public void multiTypeTest() {
        List<IValueObject> list = Arrays.asList(
                new ValueObjectA("vo1"), new ValueObjectB("vo2"), new ValueObjectA("vo3")
        );

        Gson gson = new GsonBuilder()
                .registerTypeAdapter(IValueObject.class, IValueObject.JSON_SERIALIZER)
                .create();
        String json = gson.toJson(list);
        System.out.println(json);
    }

上記を実行すると以下のようになります。

[{"value":"vo1"},{"value":"vo2"},{"value":"vo3"}]

実装クラスではなくインターフェースの指定では有効にならないようです。

作成物

今回の作成物は以下に置いています。このページに記載したもの以外にも色々と試したコードも置いているので、興味がある方は参照してもらえればと思います。

https://github.com/masaki-code/java/tree/master/gson/src/main/java/net/masaki_blog/gson/interfaces

コメント

タイトルとURLをコピーしました