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.

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

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>

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

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

Ответ 1

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

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

private transient String name;

Подробнее в документации Gson

Ответ 2

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

@Expose private Long id;

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

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

Ответ 3

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

Ответ 4

Прочитав все доступные ответы, я узнал, что наиболее гибким, в моем случае, было использование пользовательской аннотации @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();

Ответ 5

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

Ответ 6

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

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

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

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

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

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

Ответ 7

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

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

Ответ 8

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

Ответ 9

Или можно сказать, какие поля не будут отображаться с помощью:

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

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

private **transient** boolean nameAttribute;

Ответ 10

Я использовал эту стратегию: Я исключил каждое поле, которое не, помечено аннотацией @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": "Я увижу это" }

Ответ 11

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

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

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

Ответ 12

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

Ответ 13

Я работаю, просто помещая аннотацию @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);
}

Ответ 14

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

Ответ 15

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

Поведение по умолчанию, реализованное в 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);
  }