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

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

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

Изменить : я пытаюсь увидеть, возможно ли эмулировать поведение 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. Я также не хочу исключать страну как тип. Таким образом, типы одинаковы, это фактическое место в графе объектов, которое я хочу точно определить и исключить.

Ливиу Т.
источник
1
Начиная с версии 2.2, я все еще не могу указать путь к полю, который нужно исключить. flexjson.sourceforge.net чувствует себя как хорошая альтернатива.
Ливиу Т.
Посмотрите на мой ответ на довольно похожий вопрос. Он основан на создании пользовательского JsonSerializerдля некоторого типа - Countryв вашем случае - для которого затем применяется, ExclusionStrategyкоторый решает, какие поля для сериализации.
Pirho

Ответы:

625

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

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

private transient String name;

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

Крис Селин
источник
6
это почти то же самое, что и аннотация исключения, так как она применяется ко всем экземплярам этого класса. Я хотел динамическое исключение во время выполнения. В некоторых случаях я хочу, чтобы некоторые поля были исключены, чтобы обеспечить более легкий / ограниченный ответ, а в других я хочу, чтобы весь объект был сериализован
Ливиу Т.
34
Следует отметить, что переходные процессы влияют как на сериализацию, так и на десериализацию. Он также будет выдавать значение из сериализованного объекта, даже если оно присутствует в JSON.
Конг
3
Проблема с использованием transientвместо этого @Exposeзаключается в том, что вам все еще нужно смоделировать POJO на своем клиенте со всеми полями, которые могут быть введены. В случае с внутренним API, который может быть общим для проектов, это может быть проблематично в случае добавлены дополнительные поля. По сути, это белый список против черного списка полей.
Theblang
11
Этот подход не работал для меня, так как он не только исключил поле из сериализации gson, но и исключил поле из внутренней сериализации приложения (используя интерфейс Serializable).
ркк
8
переходный процесс предотвращает сериализацию и десериализацию поля. Это не соответствует моим потребностям.
Loenix
318

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

@Expose private Long id;

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

Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
JayPea
источник
95
Можно ли иметь что-то вроде «notExpose» и игнорировать только те случаи, когда все поля, кроме одного, должны быть сериализованы, а добавление аннотаций ко всем из них является избыточным?
Даниил Шевелев
2
@DaSh У меня недавно был такой сценарий. Было очень легко написать пользовательскую ExclusionStrategy, которая делала именно это. Смотрите ответ Нишанта. Единственная проблема заключалась в том, чтобы включить кучу контейнерных классов и скрипку с skipclass против skipfield (поля могут быть классами ...)
keyser
1
@DaSh Мой ответ ниже делает именно это.
Дерек Шокей
Какое отличное решение. Я столкнулся со сценарием, в котором я хочу, чтобы поле, сериализованное на диск, было проигнорировано при отправке его на сервер через gson. Отлично, спасибо!
Слинк
1
@Danlil Вы должны быть в состоянии использовать @Expose (serialize = false, deserialize = false)
Hrk
238

Итак, вы хотите исключить 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 }}
Nishant
источник
Спасибо за ответ. То, что я хочу, - это создать ExclusionStrategy, которая может принимать строку наподобие country.nameи исключать поле только nameпри сериализации поля country. Он должен быть достаточно универсальным, чтобы применяться к каждому классу, у которого есть свойство с именем страна класса Country. Я не хочу создавать стратегию исключения для каждого класса
Ливиу Т.
@ Ливиу Т. Я обновил ответ. Это требует общего подхода. Вы можете стать еще более креативным, но я сохранил это элементарно.
Nishant
Ты для обновления. То, что я пытаюсь увидеть, возможно ли узнать, где в графе объектов я нахожусь, когда он вызвал метод, так что я могу исключить некоторые поля страны, но не страну (например), так же класс, но разные свойства. Я отредактировал свой вопрос, чтобы уточнить, чего я пытаюсь достичь
Ливиу Т.
Есть ли способ исключить поля, которые имеют пустые значения?
Юсуф К.
12
Этот ответ должен быть помечен как предпочтительный ответ. В отличие от других ответов, которые в настоящее время имеют больше голосов, это решение не требует изменения класса компонента. Это огромный плюс. Что, если кто-то еще использовал тот же класс bean-компонента, и вы отметили поле, которое они хотели сохранить, как «переходное»?
user64141
223

Прочитав все доступные ответы, я обнаружил, что наиболее гибким в моем случае было использование пользовательских @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();
КРП
источник
16
В качестве дополнительного примечания, если вы хотите использовать свою стратегию исключения только для сериализации или только десериализации, используйте: addSerializationExclusionStrategyили addDeserializationExclusionStrategyвместоsetExclusionStrategies
GLee
9
Отлично! Переходное решение не работает для меня, потому что я использую Realm для БД, и я хочу исключить поле только из Gson, но не Realm (который делает переходный процесс)
Марсио Гранзотто
3
Это должен быть принятый ответ. Чтобы игнорировать пустые поля, просто измените f.getAnnotation(Exclude.class) != nullнаf.getAnnotation(Exclude.class) == null
Sharp Edge
3
Отлично, когда вы не можете использовать transientиз-за потребностей других библиотек. Спасибо!
Мартин Д
1
Также отлично подходит для меня, потому что Android сериализует мои занятия между действиями, но я хочу, чтобы они исключались только при использовании GSON. Это позволит моему приложению продолжать работать так же, пока оно не захочет обернуть их для отправки другим.
ThePartyTurtle
81

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

Дерек Шокей
источник
Для эффективности я вижу пример использования @Expose (serialize = false, deserialize = false) вместо переходного процесса.
Пайего
1
@paiego Можете ли вы расширить это? Сейчас я много лет отстранен от использования Gson, и я не понимаю, почему аннотация более эффективна, чем пометка переходных.
Дерек Шокей
Ааа, я ошибся, спасибо, что поймали это. Я ошибочно принял изменчивый за переходный процесс. (Например, нет никакого кеширования и, следовательно, нет проблем с когерентностью кеша с volatile, но это менее производительно) В любом случае, ваш код работал отлично!
Пайего
18

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

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

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

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

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

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

Клаудия Ржевницкая
источник
3
Интересно, слишком ли это сильно сказывается на производительности, если вы хотите избавиться от сложного свойства, которое нужно проанализировать перед удалением. Мысли?
Бен
Определенно не масштабируемое решение, представьте себе всю головную боль, через которую вам придется пройти, если вы измените структуру вашего объекта или добавите / удалите материал.
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();

    }
}

Чтобы использовать, создайте два списка (каждый необязательно), и создайте свой объект 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);
}
Доменик Д.
источник
Конечно, это можно изменить, чтобы посмотреть полное имя атрибута и исключить его при совпадении ...
Доменик Д.
Я делаю ниже пример. Это не работает. Просьба предложить частный статический финал Gson GSON; static {List <String> fieldExclusion = new ArrayList <String> (); fieldExclusions.add ( "ID"); GSON = GsonFactory.build (fieldExclusion, null); } приватная статическая строка getSomeJson () {String jsonStr = "[{\" id \ ": 111, \" name \ ": \" praveen \ ", \" age \ ": 16}, {\" id \ ": 222, \ "имя \": \ "Prashant \", \ "возраст \": 20}] "; вернуть jsonStr; } public static void main (String [] args) {String jsonStr = getSomeJson (); System.out.println (GSON.toJson (jsonStr)); }
Правин Хиремат
14

Я решил эту проблему с помощью пользовательских аннотаций. Это мой класс аннотации "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;
}
Hibbem
источник
5
Gson: Как исключить определенные поля из сериализации без аннотаций
Бен
3
Вы также должны добавить @Retention(RetentionPolicy.RUNTIME)к своей аннотации.
Давид Новак
9

Или можете сказать, что поля не будут выставлены с:

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

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

private **transient** boolean nameAttribute;
lucasddaniel
источник
17
Переходные и статические поля исключены по умолчанию; нет необходимости призывать excludeFieldsWithModifiers()это.
Дерек Шокей
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": "Я увижу это"}

MadMurdok
источник
6

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

@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"}
lookashc
источник
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);
}
devitoper
источник
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, заключается в том, что нулевые поля объекта игнорируются.

Означает, что объект 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);
  }
Джулиан Соларте
источник