Gson:注釈なしでシリアライゼーションから特定のフィールドを除外する方法


413

私はGsonを学習しようとしていますが、フィールドの除外に苦労しています。ここに私のクラスがあります

public class Student {    
  private Long                id;
  private String              firstName        = "Philip";
  private String              middleName       = "J.";
  private String              initials         = "P.F";
  private String              lastName         = "Fry";
  private Country             country;
  private Country             countryOfBirth;
}

public class Country {    
  private Long                id;
  private String              name;
  private Object              other;
}

GsonBuilderを使用して、firstNameまたはなどのフィールド名にExclusionStrategyを追加できますが、などcountryの特定のフィールドのプロパティを除外することができませんcountry.name

メソッドを使用するとpublic boolean shouldSkipField(FieldAttributes fa)、FieldAttributesには、フィールドをのようなフィルタと照合するのに十分な情報が含まれませんcountry.name

PS:これを改善し、RegExを使用してフィールドを除外したいので、注釈を避けたいです。

編集Struts2 JSONプラグインの動作をエミュレートできるかどうかを確認しようとしています

Gsonの使用

<interceptor-ref name="json">
  <param name="enableSMD">true</param>
  <param name="excludeProperties">
    login.password,
    studentList.*\.sin
  </param>
</interceptor-ref>

編集: 私は次の追加で質問を再開しました:

この問題をさらに明確にするために、同じタイプの2番目のフィールドを追加しました。基本的に私は除外したいが、したくcountry.nameないcountrOfBirth.name。また、国をタイプから除外したくありません。そのため、タイプは同じですが、オブジェクトグラフ内で実際に特定して除外したい場所です。


1
バージョン2.2でも、除外するフィールドへのパスを指定することはできません。flexjson.sourceforge.netは良い代替手段のように感じます。
Liviu T.

よく似た質問に対する私の答えを見てみましょう。これはJsonSerializer、何らかのタイプのカスタムを作成することに基づいていCountryます- あなたの場合- それに適用され、ExclusionStrategyシリアル化するフィールドを決定します。
ピロー2017

回答:


625

一般的にシリアル化したくないフィールドは、「transient」修飾子を使用する必要があります。これは、jsonシリアライザにも適用されます(少なくとも、gsonを含め、使用したいくつかのシリアライザには適用されます)。

シリアル化されたjsonに名前を表示したくない場合は、一時的なキーワードを指定します。例:

private transient String name;

Gsonドキュメントの詳細


6
これは、そのクラスのすべてのインスタンスに適用されるため、除外アノテーションとほとんど同じです。ランタイムの動的除外が必要でした。より軽い/制限された応答を提供するために一部のフィールドを除外したい場合と、完全なオブジェクトをシリアル化したい場合があります
Liviu T.

34
注意すべきことの1つは、一時的な影響がシリアライズとデシリアライズの両方に影響することです。また、JSONに存在する場合でも、オブジェクトにシリアル化された値を出力します。
コング

3
transient代わりにを使用する場合の問題@Exposeは、すべてのフィールドが含まれる可能性のあるクライアントでPOJOをモックアップする必要があることです。プロジェクト間で共有される可能性のあるバックエンドAPIの場合、これは問題となる可能性があります追加のフィールドが追加されます。基本的には、フィールドのホワイトリストvsブラックリストです。
theblang 2014年

11
この方法は、gsonシリアル化からフィールドを除外しただけでなく、(Serializableインターフェイスを使用して)内部アプリシリアル化からフィールドを除外したため、私にとってはうまくいきませんでした。
2015年

8
トランジェントは、フィールドのシリアル化と非シリアル化を防止します。これは私のニーズと一致しません。
Loenix 2015

318

Nishantは優れたソリューションを提供しましたが、もっと簡単な方法があります。次のように、@ Exposeアノテーションで必要なフィールドをマークするだけです。

@Expose private Long id;

シリアル化したくないフィールドは省略してください。次に、この方法でGsonオブジェクトを作成します。

Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();

95
「notExpose」のようなものを使用して、1つを除くすべてのフィールドをシリアル化する必要があり、それらのすべてに注釈を追加する必要がある場合にのみ無視することは可能ですか?
Daniil Shevelev、2014

2
@DaSh最近このようなシナリオがありました。まさにそれを行うカスタムExclusionStrategyを書くのはとても簡単でした。ニシャントの答えを見てください。唯一の問題は、一連のコンテナークラスを含め、skipclassとskipfieldをいじることでした(フィールドはクラスにすることができます...)
キーサー

1
@DaSh 以下の私の答えはまさにそれを行います。
デレクショッキー2014

なんて素晴らしいソリューションでしょう。フィールドをディスクにシリアル化したいが、それをgson経由でサーバーに送信するときに無視したいというシナリオに遭遇していました。パーフェクト、ありがとう!
Slynk、2015

1
@Danlil @Expose(serialize = false、deserialize = false)を使用できるはずです
Hrk

237

したがって、あなたは除外 firstNameしたいとしcountry.nameます。これはあなたExclusionStrategyがどのように見えるべきかです

    public class TestExclStrat implements ExclusionStrategy {

        public boolean shouldSkipClass(Class<?> arg0) {
            return false;
        }

        public boolean shouldSkipField(FieldAttributes f) {

            return (f.getDeclaringClass() == Student.class && f.getName().equals("firstName"))||
            (f.getDeclaringClass() == Country.class && f.getName().equals("name"));
        }

    }

よく見るとtrueStudent.firstNameand に戻りますがCountry.name、これは除外したいものです。

これをこのExclusionStrategyように適用する必要があります。

    Gson gson = new GsonBuilder()
        .setExclusionStrategies(new TestExclStrat())
        //.serializeNulls() <-- uncomment to serialize NULL fields as well
        .create();
    Student src = new Student();
    String json = gson.toJson(src);
    System.out.println(json);

これは次を返します:

{ "middleName": "J.", "initials": "P.F", "lastName": "Fry", "country": { "id": 91}}

国のオブジェクトはid = 91L学生のクラスで初期化されていると思います。


ファンシーになるかもしれません。たとえば、名前に「name」文字列を含むフィールドをシリアル化したくない場合です。これを行う:

public boolean shouldSkipField(FieldAttributes f) {
    return f.getName().toLowerCase().contains("name"); 
}

これは戻ります:

{ "initials": "P.F", "country": { "id": 91 }}

編集:要求に応じて詳細情報を追加しました。

これでExclusionStrategy十分ですが、「完全修飾フィールド名」を渡す必要があります。下記参照:

    public class TestExclStrat implements ExclusionStrategy {

        private Class<?> c;
        private String fieldName;
        public TestExclStrat(String fqfn) throws SecurityException, NoSuchFieldException, ClassNotFoundException
        {
            this.c = Class.forName(fqfn.substring(0, fqfn.lastIndexOf(".")));
            this.fieldName = fqfn.substring(fqfn.lastIndexOf(".")+1);
        }
        public boolean shouldSkipClass(Class<?> arg0) {
            return false;
        }

        public boolean shouldSkipField(FieldAttributes f) {

            return (f.getDeclaringClass() == c && f.getName().equals(fieldName));
        }

    }

これを一般的に使用する方法を次に示します。

    Gson gson = new GsonBuilder()
        .setExclusionStrategies(new TestExclStrat("in.naishe.test.Country.name"))
        //.serializeNulls()
        .create();
    Student src = new Student();
    String json = gson.toJson(src);
    System.out.println(json); 

それは返します:

{ "firstName": "Philip" , "middleName": "J.", "initials": "P.F", "lastName": "Fry", "country": { "id": 91 }}

お返事ありがとうございます。私が欲しいのは、文字列を取りcountry.name、フィールドnameをシリアル化するときにのみフィールドを除外できるExclusionStrategyを作成することcountryです。これは、Countryクラスのcountryという名前のプロパティを持つすべてのクラスに適用するのに十分一般的でなければなりません。すべてのクラスにExclusionStrategyを作成したくありません
リビウT.

@Liviu T.回答を更新しました。それは一般的なアプローチを取ります。あなたはさらに創造的になるかもしれませんが、私はそれを素朴に保ちました。
Nishant '26年

更新のTy。オブジェクトグラフ内のどこにあるかを知ることができるかどうかを確認するために私が試みているのは、メソッドが呼び出されたときに、countryOfBirthではなく、countryの一部のフィールドを除外できるため、同じクラスで異なるプロパティです。私が達成しようとしていることを明確にするために質問を編集しました
Liviu T.

空の値を持つフィールドを除外する方法はありますか?
ユスフK.

12
この回答は、優先回答としてマークする必要があります。現在投票数が多い他の回答とは異なり、このソリューションではBeanクラスを変更する必要はありません。これは大きなプラスです。他の誰かが同じBeanクラスを使用していて、永続化したいフィールドを「一時的」としてマークした場合はどうなりますか?
user64141

221

私が見つけたすべての利用可能な回答を読んだ後、私の場合、最も柔軟なのはカスタム@Excludeアノテーションを使用することでした。だから、私はこれのために簡単な戦略を実装しました(アプリのシリアル@Exposetransientで競合するものを使用してすべてのフィールドにマークを付けたくなかったし、使用したくなかったSerializable):

注釈:

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

戦略:

public class AnnotationExclusionStrategy implements ExclusionStrategy {

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

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

使用法:

new GsonBuilder().setExclusionStrategies(new AnnotationExclusionStrategy()).create();

16
追加の注記として、シリアライゼーションのみまたはデシリアライゼーションのみに除外戦略を使用したい場合は、以下を使用します。addSerializationExclusionStrategyまたは、addDeserializationExclusionStrategy代わりにsetExclusionStrategies
GLee

9
パーフェクト!私はRealm for DBを使用していて、Gsonからのみフィールドを除外し、Realmからは除外したいので、一時的な解決策は機能しません(一時的なものは除外します)
Marcio Granzotto

3
これは受け入れられる答えになるはずです。nullフィールドを無視するには、次のように変更f.getAnnotation(Exclude.class) != nullしますf.getAnnotation(Exclude.class) == null
Sharp Edge

3
transient他のライブラリのニーズのために使用できない場合に最適です。ありがとう!
マーティンD

1
Androidはアクティビティ間でクラスをシリアル化するため、私にとっても素晴らしいですが、GSONを使用する場合にのみ、それらを除外したいのです。これで、他のユーザーに送信するためにまとめるまで、アプリは同じように機能し続けます。
ThePartyTurtle 2018年

81

この問題に遭遇しました。シリアル化からのみ除外したいフィールドが少数あったため、Gsonの @Exposeアノテーションとカスタム除外戦略。

組み込みの唯一の使用方法@Exposeはを設定することですGsonBuilder.excludeFieldsWithoutExposeAnnotation()が、その名前が示すように、明示的でないフィールド@Exposeは無視されます。除外したいフィールドが少ししかなかったので、すべてのフィールドに注釈を追加する可能性は非常に面倒でした。

私は逆を効果的に望んでいました@Expose。明示的に除外に使用しない限り、すべてが含まれています。これを実現するために、次の除外戦略を使用しました。

new GsonBuilder()
        .addSerializationExclusionStrategy(new ExclusionStrategy() {
            @Override
            public boolean shouldSkipField(FieldAttributes fieldAttributes) {
                final Expose expose = fieldAttributes.getAnnotation(Expose.class);
                return expose != null && !expose.serialize();
            }

            @Override
            public boolean shouldSkipClass(Class<?> aClass) {
                return false;
            }
        })
        .addDeserializationExclusionStrategy(new ExclusionStrategy() {
            @Override
            public boolean shouldSkipField(FieldAttributes fieldAttributes) {
                final Expose expose = fieldAttributes.getAnnotation(Expose.class);
                return expose != null && !expose.deserialize();
            }

            @Override
            public boolean shouldSkipClass(Class<?> aClass) {
                return false;
            }
        })
        .create();

これで、@Expose(serialize = false)または@Expose(deserialize = false)アノテーションを使用していくつかのフィールドを簡単に除外できます(両方の@Expose属性のデフォルト値はであることに注意してくださいtrue)。もちろんを使用することもできますが、代わり@Expose(serialize = false, deserialize = false)にフィールドtransientを宣言することで、より簡潔に実現できます(これらのカスタム除外戦略でも有効です)。


効率を上げるために、一時的ではなく@Expose(serialize = false、deserialize = false)を使用するケースを確認できます。
パイエゴ2018

1
@paiego拡大できますか?私は今、Gsonの使用から何年も離れており、注釈が一時的であるとマークするよりもなぜ効率的であるのか理解できません。
デレクショッキー2018

ああ、私は間違いを犯した、これを捕まえてくれてありがとう。揮発性を一時的なものと間違えました。(例:キャッシングがないため、揮発性のキャッシュコヒーレンシの問題はありませんが、パフォーマンスは低下します)とにかく、コードはうまく機能しました!
パイエゴ2018

18

gsonでjsonツリーを探索できます。

このようなものを試してください:

gson.toJsonTree(student).getAsJsonObject()
.get("country").getAsJsonObject().remove("name");

いくつかのプロパティを追加することもできます:

gson.toJsonTree(student).getAsJsonObject().addProperty("isGoodStudent", false);

gson 2.2.4でテスト済み。


3
削除する前に解析する必要のある複雑なプロパティを削除したい場合は、これがパフォーマンスへの影響が大きすぎるのではないかと思います。考え?
2016年

確かにスケーラブルなソリューションではありません。オブジェクトの構造を変更したり、オブジェクトを追加/削除したりする場合に経験する必要があるすべての頭痛を想像してください。
codenamezero

16

この機能をサポートするためのクラスファクトリを思いつきました。除外するフィールドまたはクラスの任意の組み合わせを渡します。

public class GsonFactory {

    public static Gson build(final List<String> fieldExclusions, final List<Class<?>> classExclusions) {
        GsonBuilder b = new GsonBuilder();
        b.addSerializationExclusionStrategy(new ExclusionStrategy() {
            @Override
            public boolean shouldSkipField(FieldAttributes f) {
                return fieldExclusions == null ? false : fieldExclusions.contains(f.getName());
            }

            @Override
            public boolean shouldSkipClass(Class<?> clazz) {
                return classExclusions == null ? false : classExclusions.contains(clazz);
            }
        });
        return b.create();

    }
}

使用するには、2つのリストを作成し(それぞれオプションです)、GSONオブジェクトを作成します。

static {
 List<String> fieldExclusions = new ArrayList<String>();
 fieldExclusions.add("id");
 fieldExclusions.add("provider");
 fieldExclusions.add("products");

 List<Class<?>> classExclusions = new ArrayList<Class<?>>();
 classExclusions.add(Product.class);
 GSON = GsonFactory.build(null, classExclusions);
}

private static final Gson GSON;

public String getSomeJson(){
    List<Provider> list = getEntitiesFromDatabase();
    return GSON.toJson(list);
}

もちろん、これを変更して属性の完全修飾名を確認し、一致時にそれを除外することができます...
Domenic D.

以下の例をやっています。これは機能していません。Plsはプライベートな静的最終Gson GSONを提案します。static {List <String> fieldExclusions = new ArrayList <String>(); fieldExclusions.add( "id"); GSON = GsonFactory.build(fieldExclusions、null); } private static String getSomeJson(){String jsonStr = "[{\" id \ ":111、\" name \ ":\" praveen \ "、\" age \ ":16}、{\" id \ ": 222、\ "name \":\ "prashant \"、\ "age \":20}] "; jsonStrを返します。} public static void main(String [] args){String jsonStr = getSomeJson(); System.out.println(GSON.toJson(jsonStr)); }
Praveen Hiremath

13

この問題をカスタムアノテーションで解決しました。これは私の「SkipSerialization」アノテーションクラスです。

@Target (ElementType.FIELD)
public @interface SkipSerialisation {

}

これは私のGsonBuilderです:

gsonBuilder.addSerializationExclusionStrategy(new ExclusionStrategy() {

  @Override public boolean shouldSkipField (FieldAttributes f) {

    return f.getAnnotation(SkipSerialisation.class) != null;

  }

  @Override public boolean shouldSkipClass (Class<?> clazz) {

    return false;
  }
});

例:

public class User implements Serializable {

  public String firstName;

  public String lastName;

  @SkipSerialisation
  public String email;
}

5
Gson:注釈なしで
Ben

3
@Retention(RetentionPolicy.RUNTIME)アノテーションにも追加する必要があります。
DavidNovák2017年

9

または、whatsフィールドが公開しないと言うことができます:

Gson gson = gsonBuilder.excludeFieldsWithModifiers(Modifier.TRANSIENT).create();

あなたのクラスの属性:

private **transient** boolean nameAttribute;

17
一時フィールドと静的フィールドはデフォルトで除外されます。それを要求する必要はありませんexcludeFieldsWithModifiers()
デレクショッキー2013

9

私はこの戦略を使用しました:@SerializedNameアノテーションが付いていないすべてのフィールドを除外しました

public class Dummy {

    @SerializedName("VisibleValue")
    final String visibleValue;
    final String hiddenValue;

    public Dummy(String visibleValue, String hiddenValue) {
        this.visibleValue = visibleValue;
        this.hiddenValue = hiddenValue;
    }
}


public class SerializedNameOnlyStrategy implements ExclusionStrategy {

    @Override
    public boolean shouldSkipField(FieldAttributes f) {
        return f.getAnnotation(SerializedName.class) == null;
    }

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


Gson gson = new GsonBuilder()
                .setExclusionStrategies(new SerializedNameOnlyStrategy())
                .create();

Dummy dummy = new Dummy("I will see this","I will not see this");
String json = gson.toJson(dummy);

戻る

{"VisibleValue": "これが表示されます"}


6

別のアプローチ(特に、実行時にフィールドを除外することを決定する必要がある場合に役立ちます)は、gsonインスタンスにTypeAdapterを登録することです。以下の例:

Gson gson = new GsonBuilder()
.registerTypeAdapter(BloodPressurePost.class, new BloodPressurePostSerializer())

以下のケースでは、サーバーは2つの値のいずれかを期待しますが、どちらもintであるため、gsonは両方をシリアル化します。私の目標は、サーバーに投稿されるjsonからゼロ(またはそれ以下)の値を省略することでした。

public class BloodPressurePostSerializer implements JsonSerializer<BloodPressurePost> {

    @Override
    public JsonElement serialize(BloodPressurePost src, Type typeOfSrc, JsonSerializationContext context) {
        final JsonObject jsonObject = new JsonObject();

        if (src.systolic > 0) {
            jsonObject.addProperty("systolic", src.systolic);
        }

        if (src.diastolic > 0) {
            jsonObject.addProperty("diastolic", src.diastolic);
        }

        jsonObject.addProperty("units", src.units);

        return jsonObject;
    }
}

6

Kotlinの@Transient注釈も、明らかにトリックを実行します。

data class Json(
    @field:SerializedName("serialized_field_1") val field1: String,
    @field:SerializedName("serialized_field_2") val field2: String,
    @Transient val field3: String
)

出力:

{"serialized_field_1":"VALUE1","serialized_field_2":"VALUE2"}

1

私は@Expose注釈を置くだけで作業しています、ここで使用する私のバージョン

compile 'com.squareup.retrofit2:retrofit:2.0.2'
compile 'com.squareup.retrofit2:converter-gson:2.0.2'

ではModelクラス:

@Expose
int number;

public class AdapterRestApi {

ではAdapterクラス:

public EndPointsApi connectRestApi() {
    OkHttpClient client = new OkHttpClient.Builder()
            .connectTimeout(90000, TimeUnit.SECONDS)
            .readTimeout(90000,TimeUnit.SECONDS).build();

    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(ConstantRestApi.ROOT_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .client(client)
            .build();

    return retrofit.create  (EndPointsApi.class);
}

1

Kotlinバージョンがあります

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FIELD)
internal annotation class JsonSkip

class SkipFieldsStrategy : ExclusionStrategy {

    override fun shouldSkipClass(clazz: Class<*>): Boolean {
        return false
    }

    override fun shouldSkipField(f: FieldAttributes): Boolean {
        return f.getAnnotation(JsonSkip::class.java) != null
    }
}

これをRetrofit GSONConverterFactoryに追加する方法:

val gson = GsonBuilder()
                .setExclusionStrategies(SkipFieldsStrategy())
                //.serializeNulls()
                //.setDateFormat(DateFormat.LONG)
                //.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
                //.setPrettyPrinting()
                //.registerTypeAdapter(Id.class, IdTypeAdapter())
                .create()
        return GsonConverterFactory.create(gson)

0

これは私がいつも使うものです:

Gsonに実装されているデフォルトの動作では、nullオブジェクトフィールドは無視されます。

つまり、Gsonオブジェクトはnull値を持つフィールドをJSONにシリアル化しません。Javaオブジェクトのフィールドがnullの場合、Gsonはそれを除外します。

この関数を使用して、一部のオブジェクトをnullに変換したり、独自に設定したりできます

     /**
   * convert object to json
   */
  public String toJson(Object obj) {
    // Convert emtpy string and objects to null so we don't serialze them
    setEmtpyStringsAndObjectsToNull(obj);
    return gson.toJson(obj);
  }

  /**
   * Sets all empty strings and objects (all fields null) including sets to null.
   *
   * @param obj any object
   */
  public void setEmtpyStringsAndObjectsToNull(Object obj) {
    for (Field field : obj.getClass().getDeclaredFields()) {
      field.setAccessible(true);
      try {
        Object fieldObj = field.get(obj);
        if (fieldObj != null) {
          Class fieldType = field.getType();
          if (fieldType.isAssignableFrom(String.class)) {
            if(fieldObj.equals("")) {
              field.set(obj, null);
            }
          } else if (fieldType.isAssignableFrom(Set.class)) {
            for (Object item : (Set) fieldObj) {
              setEmtpyStringsAndObjectsToNull(item);
            }
            boolean setFielToNull = true;
            for (Object item : (Set) field.get(obj)) {
              if(item != null) {
                setFielToNull = false;
                break;
              }
            }
            if(setFielToNull) {
              setFieldToNull(obj, field);
            }
          } else if (!isPrimitiveOrWrapper(fieldType)) {
            setEmtpyStringsAndObjectsToNull(fieldObj);
            boolean setFielToNull = true;
            for (Field f : fieldObj.getClass().getDeclaredFields()) {
              f.setAccessible(true);
              if(f.get(fieldObj) != null) {
                setFielToNull = false;
                break;
              }
            }
            if(setFielToNull) {
              setFieldToNull(obj, field);
            }
          }
        }
      } catch (IllegalAccessException e) {
        System.err.println("Error while setting empty string or object to null: " + e.getMessage());
      }
    }
  }

  private void setFieldToNull(Object obj, Field field) throws IllegalAccessException {
    if(!Modifier.isFinal(field.getModifiers())) {
      field.set(obj, null);
    }
  }

  private boolean isPrimitiveOrWrapper(Class fieldType)  {
    return fieldType.isPrimitive()
        || fieldType.isAssignableFrom(Integer.class)
        || fieldType.isAssignableFrom(Boolean.class)
        || fieldType.isAssignableFrom(Byte.class)
        || fieldType.isAssignableFrom(Character.class)
        || fieldType.isAssignableFrom(Float.class)
        || fieldType.isAssignableFrom(Long.class)
        || fieldType.isAssignableFrom(Double.class)
        || fieldType.isAssignableFrom(Short.class);
  }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.