it-swarm.com.ru

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 и добавить ExclusionStrategy для имени поля, такого как firstName или country, но мне не удается исключить свойства некоторых полей, таких как country.name.

Используя метод public boolean shouldSkipField(FieldAttributes fa), FieldAttributes не содержит достаточно информации, чтобы сопоставить поле с фильтром, подобным country.name.

Буду признателен за любую помощь в решении этой проблемы.

П.С .: Я хочу избегать аннотаций, так как я хочу улучшить это и использовать RegEx для фильтрации полей.

Спасибо

Edit: я пытаюсь понять, возможно ли эмулировать поведение плагина JSON Struts2

используя Gson

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

Правка: Я вновь открыл вопрос со следующим дополнением:

Я добавил второе поле того же типа, чтобы прояснить эту проблему. В основном я хочу исключить country.name, но не countrOfBirth.name. Я также не хочу исключать страну как тип . Таким образом, типы одинаковы, это фактическое место в графе объектов, которое я хочу точно определить и исключить.

386
Liviu T.

Любые поля, которые вы не хотите сериализовать, следует использовать модификатор «transient», и это также относится к сериализаторам json (по крайней мере, к некоторым, которые я использовал, включая gson).

Если вы не хотите, чтобы имя отображалось в сериализованном json, дайте ему временное ключевое слово, например:

private transient String name;

Более подробная информация в документации Gson

585
Chris Seline

Нишант предоставил хорошее решение, но есть более простой способ. Просто отметьте нужные поля аннотацией @Expose, например:

@Expose private Long id;

Оставьте все поля, которые вы не хотите сериализовать. Затем просто создайте свой объект Gson следующим образом:

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

Итак, вы хотите исключить 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"));
        }

    }

Если вы внимательно посмотрите, он возвращает true для Student.firstName и 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", "инициалы": "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 }}
218
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();
191
pkk

Я столкнулся с этой проблемой, в которой у меня было небольшое количество полей, которые я хотел исключить только из сериализации, поэтому я разработал довольно простое решение, которое использует аннотацию @Expose от Gson с пользовательскими стратегиями исключения.

Единственный встроенный способ использования @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 (которое все еще действует с этими пользовательскими стратегиями исключения).

75
Derek Shockey

Я придумал фабрику классов для поддержки этой функциональности. Передайте любую комбинацию полей или классов, которые вы хотите исключить.

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();

    }
}

Чтобы использовать, создайте два списка (каждый необязательно), и создайте свой объект 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.

Вы можете исследовать дерево JSON с помощью GSON.

Попробуйте что-то вроде этого: 

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

Вы также можете добавить некоторые свойства:

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

Протестировано с помощью gson 2.2.4.

15
Klaudia Rzewnicka

Я решил эту проблему с помощью пользовательских аннотаций . Это мой класс аннотаций "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();

в вашем классе по атрибуту:

private **transient** boolean nameAttribute;
8
lucasddaniel

Я использовал эту стратегию: Я исключил все поля, которые не / помечены @SerializedName annotation, т.е.

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
MadMurdok

Другой подход (особенно полезный, если вам необходимо принять решение об исключении поля во время выполнения), заключается в регистрации TypeAdapter в вашем экземпляре gson. Пример ниже:

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

В приведенном ниже случае сервер ожидает одно из двух значений, но, поскольку они оба являются целыми числами, 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

@Transientantotation Котлина, по-видимому, тоже помогает.

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

Это то, что я всегда использую:

Поведение по умолчанию, реализованное в Gson, заключается в том, что нулевые поля объекта игнорируются.

Означает, что объект Gson не сериализует поля с нулевыми значениями в JSON. Если поле в объекте Java является нулевым, Gson исключает его.

Вы можете использовать эту функцию, чтобы преобразовать какой-либо объект в ноль или установить его самостоятельно

     /**
   * 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

У меня есть версия 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
Michał Ziobro