web-dev-qa-db-ja.com

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

私は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を使用してfirstNamecountryなどのフィールド名にExclusionStrategyを追加することはできますが、country.nameなどの特定のフィールドのプロパティを除外できないようです。

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

私はこの問題に対する解決策の助けをいただければ幸いです。

P.S:これを改善し、フィールドを除外するために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は除外したくありません。また、国をタイプとして除外したくありません。それで、タイプは同じで、オブジェクトグラフ内で実際に特定して除外したい場所です。

393
Liviu T.

一般的にシリアライズしたくないフィールドはすべて "transient"修飾子を使うべきです。これはjsonシリアライザにも当てはまります(少なくとも、gsonを含めて私が使ったことのあるものには当てはまります)。

直列化されたJSONに名前を表示したくない場合は、一時的なキーワードを付けます。

private transient String name;

Gsonのドキュメントの詳細

596
Chris Seline

Nishantは良い解決策を提供しましたが、もっと簡単な方法があります。次のように、@ Exposeアノテーションを使用して目的のフィールドにマークを付けるだけです。

@Expose private Long id;

直列化したくないフィールドは残してください。それなら、このようにGsonオブジェクトを作成してください。

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

だから、 exclude firstNamecountry.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"));
        }

    }

よく見ると、Student.firstNameCountry.nameにはtrueが返されます。

このように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。"、 "イニシャル": "P.F"、 "lastName": "フライ"、 "国":{"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 }}
221
Nishant

私が見つけた利用可能なすべての答えを読んだ後、私の場合、最も柔軟なのはカスタム@Excludeアノテーションを使うことでした。だから、私はこれのための簡単な戦略を実装しました(私は@Exposeを使ってすべてのフィールドをマークしたくもなく、アプリのtransientシリアライゼーションで競合した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();
197
pkk

私はこの問題に遭遇しました。そこでは、直列化からのみ除外したいフィールドがいくつかあったので、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を宣言することでより簡潔に達成されます(これはまだこれらのカスタム除外戦略で有効です)。

76
Derek Shockey

あなたはgsonでjsonツリーを探索することができます。

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

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

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

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

Gson 2.2.4でテスト済み。

17

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

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);
}
16
Domenic D.

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

@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;
}
13
Reyske

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

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

あなたのクラスではattribute:

private **transient** boolean nameAttribute;
8
lucasddaniel

私はこの戦略を使用しました:not@ 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": "これが表示されます"}

7
MadMurdok

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

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

以下のケースでは、サーバーは2つの値のうちの1つを期待しますが、それらは両方とも整数なので、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
Damian

Kotlinの@Transientannotationもこのトリックを明らかにしています。

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"}
2
lookashc

私は@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
devitoper

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)
1
Michał Ziobro

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

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);
  }
0
Julian Solarte