Gsonの直列化からフィールドを除外する

Gsonのシリアル化からフィールドを除外する

1. 概要

この短いチュートリアルでは、Javaクラスとそのサブクラスの1つ以上のフィールドをGsonシリアル化から除外するために使用できるオプションについて説明します。

2. 初期設定

まず、クラスを定義しましょう。

@Data
@AllArgsConstructor
public class MyClass {
    private long id;
    private String name;
    private String other;
    private MySubClass subclass;
}

@Data
@AllArgsConstructor
public class MySubClass {
    private long id;
    private String description;
    private String otherVerboseInfo;
}

便宜上、Lombokで注釈を付けました(ゲッター、セッター、コンストラクターのシンタックスシュガー…)。

次に、それらにデータを入力しましょう。

MySubClass subclass = new MySubClass(42L, "the answer", "Verbose field not to serialize")
MyClass source = new MyClass(1L, "foo", "bar", subclass);

私たちの目標は、MyClass.otherフィールドとMySubClass.otherVerboseInfoフィールドがシリアル化されないようにすることです。

取得する予定の出力は次のとおりです。

{
  "id":1,
  "name":"foo",
  "subclass":{
    "id":42,
    "description":"the answer"
  }
}

Javaの場合:

String expectedResult = "{\"id\":1,\"name\":\"foo\",\"subclass\":{\"id\":42,\"description\":\"the answer\"}}";

3. 一時的な修飾子

transient修飾子を使用してフィールドをマークできます。

public class MyClass {
    private long id;
    private String name;
    private transient String other;
    private MySubClass subclass;
}

public class MySubClass {
    private long id;
    private String description;
    private transient String otherVerboseInfo;
}

Gsonシリアライザーは、一時として宣言されたすべてのフィールドを無視します。

String jsonString = new Gson().toJson(source);
assertEquals(expectedResult, jsonString);

これは非常に高速ですが、Gsonだけでなくevery serialization tool will take transient into accountという深刻な欠点もあります。

トランジェントは、シリアル化から除外するJavaの方法です。その後、フィールドは、Serializableのシリアル化、およびオブジェクトを管理するすべてのライブラリツールまたはフレームワークによっても除外されます。

さらに、transientキーワードは、シリアル化と逆シリアル化の両方で常に機能します。これは、ユースケースによっては制限される場合があります。

4. @Expose注釈

Gsoncom.google.gson.annotations @Exposeアノテーションは逆に機能します。

これを使用して、シリアル化するフィールドを宣言し、他のフィールドを無視できます。

public class MyClass {
    @Expose
    private long id;
    @Expose
    private String name;
    private String other;
    @Expose
    private MySubClass subclass;
}

public class MySubClass {
    @Expose
    private long id;
    @Expose
    private String description;
    private String otherVerboseInfo;
}

そのためには、GsonBuilderでGsonをインスタンス化する必要があります。

Gson gson = new GsonBuilder()
  .excludeFieldsWithoutExposeAnnotation()
  .create();
String jsonString = gson.toJson(source);
assertEquals(expectedResult, jsonString);

今回は、フィルタリングをシリアル化、デシリアル化、またはその両方(デフォルト)で行うかどうかをフィールドレベルで制御できます。

MyClass.otherがシリアル化されないようにする方法を見てみましょう。ただし、JSONからの逆シリアル化中にはMyClass.otherにデータを入力できます。

@Expose(serialize = false, deserialize = true)
private String other;

これはGsonが提供する最も簡単な方法であり、他のライブラリには影響しませんが、コードの冗長性を意味する可能性があります。 数百のフィールドを持つクラスがあり、1つのフィールドのみを除外する場合は、99の注釈を記述する必要がありますが、これはやり過ぎです。

5. ExclusionStrategy

高度にカスタマイズ可能なソリューションは、com.google.gson.*ExclusionStrategy*の使用です。

これにより、カスタム基準でフィールド(および/またはクラス)をシリアル化するかどうかをGsonBuilderに指示する戦略を(外部または匿名の内部クラスで)定義できます。

Gson gson = new GsonBuilder()
  .addSerializationExclusionStrategy(strategy)
  .create();
String jsonString = gson.toJson(source);

assertEquals(expectedResult, jsonString);

使用するスマート戦略の例をいくつか見てみましょう。

5.1. クラスとフィールドの名前

もちろん、1つ以上のフィールド/クラス名をハードコーディングすることもできます。

ExclusionStrategy strategy = new ExclusionStrategy() {
    @Override
    public boolean shouldSkipField(FieldAttributes field) {
        if (field.getDeclaringClass() == MyClass.class && field.getName().equals("other")) {
            return true;
        }
        if (field.getDeclaringClass() == MySubClass.class && field.getName().equals("otherVerboseInfo")) {
            return true;
        }
        return false;
    }

    @Override
    public boolean shouldSkipClass(Class clazz) {
        return false;
    }
};

これは迅速かつ正確に行われますが、あまり再利用できず、属性の名前を変更した場合にエラーが発生しやすくなります。

5.2. ビジネス基準あり

単純にブール値を返す必要があるため、そのメソッド内に任意のビジネスロジックを実装できます。

次の例では、「other」で始まるすべてのフィールドを、それらが属するクラスに関係なく、シリアル化してはならないフィールドとして識別します。

ExclusionStrategy strategy = new ExclusionStrategy() {
    @Override
    public boolean shouldSkipClass(Class clazz) {
        return false;
    }

    @Override
    public boolean shouldSkipField(FieldAttributes field) {
        return field.getName().startsWith("other");
    }
};

5.3. カスタムアノテーション付き

別のスマートなアプローチは、カスタムアノテーションを作成することです。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Exclude {}

次に、@Exposeアノテーションとまったく同じように機能させるために、ExclusionStrategyを悪用できますが、逆に:

public class MyClass {
    private long id;
    private String name;
    @Exclude
    private String other;
    private MySubClass subclass;
}

public class MySubClass {
    private long id;
    private String description;
    @Exclude
    private String otherVerboseInfo;
}

そして、ここに戦略があります:

ExclusionStrategy strategy = new ExclusionStrategy() {
    @Override
    public boolean shouldSkipClass(Class clazz) {
        return false;
    }

    @Override
    public boolean shouldSkipField(FieldAttributes field) {
        return field.getAnnotation(Exclude.class) != null;
    }
};

This StackOverflow answerは最初にこの手法を説明しました。

これにより、一度注釈と戦略を記述し、さらに修正することなく動的にフィールドに注釈を付けることができます。

5.4. 除外戦略を逆シリアル化に拡張する

どの戦略を使用する場合でも、それを適用する場所をいつでも制御できます。

シリアル化中のみ:

Gson gson = new GsonBuilder().addSerializationExclusionStrategy(strategy)

逆シリアル化中のみ:

Gson gson = new GsonBuilder().addDeserializationExclusionStrategy(strategy)

常に:

Gson gson = new GsonBuilder().setExclusionStrategies(strategy);

6. 結論

Gsonのシリアル化中に、クラスとそのサブクラスからフィールドを除外するさまざまな方法を見てきました。

また、すべてのソリューションの主な利点と落とし穴についても説明しました。

いつものように、完全なソースコードはover on Githubで利用できます。