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だけでなく、すべてのシリアル化ツールが一時的なものを考慮します

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

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

4. @Exposeアノテーション

Gson com.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からの逆シリアル化中にデータを入力できるようにします。

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

これはGsonが提供する最も簡単な方法であり、他のライブラリには影響しませんが、コードの冗長性を意味する可能性があります。 100のフィールドを持つクラスがあり、1つのフィールドだけを除外したい場合は、99のアノテーションを作成する必要がありますが、これはやり過ぎです。

5. 除外戦略

高度にカスタマイズ可能なソリューションは、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. カスタム注釈付き

もう1つの賢いアプローチは、カスタム注釈を作成することです。

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

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

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;
    }
};

このStackOverflowの回答は、最初にこの手法について説明しました。

これにより、注釈と戦略を1回記述し、それ以上変更することなくフィールドに動的に注釈を付けることができます。

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

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

シリアル化中のみ:

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

デシリアライズ中のみ:

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

いつも:

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

6. 結論

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

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

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