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を使用してfirstName
やcountry
などのフィールド名に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
は除外したくありません。また、国をタイプとして除外したくありません。それで、タイプは同じで、オブジェクトグラフ内で実際に特定して除外したい場所です。
一般的にシリアライズしたくないフィールドはすべて "transient"修飾子を使うべきです。これはjsonシリアライザにも当てはまります(少なくとも、gsonを含めて私が使ったことのあるものには当てはまります)。
直列化されたJSONに名前を表示したくない場合は、一時的なキーワードを付けます。
private transient String name;
Nishantは良い解決策を提供しましたが、もっと簡単な方法があります。次のように、@ Exposeアノテーションを使用して目的のフィールドにマークを付けるだけです。
@Expose private Long id;
直列化したくないフィールドは残してください。それなら、このようにGsonオブジェクトを作成してください。
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
だから、 exclude 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"));
}
}
よく見ると、Student.firstName
とCountry.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 }}
私が見つけた利用可能なすべての答えを読んだ後、私の場合、最も柔軟なのはカスタム@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();
私はこの問題に遭遇しました。そこでは、直列化からのみ除外したいフィールドがいくつかあったので、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
を宣言することでより簡潔に達成されます(これはまだこれらのカスタム除外戦略で有効です)。
あなたはgsonでjsonツリーを探索することができます。
このようなことを試してください:
gson.toJsonTree(student).getAsJsonObject()
.get("country").getAsJsonObject().remove("name");
いくつかのプロパティを追加することもできます。
gson.toJsonTree(student).getAsJsonObject().addProperty("isGoodStudent", false);
Gson 2.2.4でテスト済み。
この機能をサポートするためにクラスファクトリを思いつきました。除外するフィールドまたはクラスの任意の組み合わせを渡します。
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);
}
私はこの問題をカスタムアノテーションで解決しました。これは私の "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;
}
またはフィールドが何を公開しないと言うことができます:
Gson gson = gsonBuilder.excludeFieldsWithModifiers(Modifier.TRANSIENT).create();
あなたのクラスではattribute:
private **transient** boolean nameAttribute;
私はこの戦略を使用しました: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": "これが表示されます"}
もう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;
}
}
Kotlinの@Transient
annotationもこのトリックを明らかにしています。
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"}
私は@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);
}
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)
これは私がいつも使うものです:
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);
}