«1. Обзор

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

2. Начальная настройка

Давайте сначала определим наши классы:

@Data
@AllArgsConstructor
public class MyClass {
    private long id;
    private String name;
    private String other;
    private MySubClass subclass;
}

@Data
@AllArgsConstructor
public class MySubClass {
    private long id;
    private String description;
    private String otherVerboseInfo;
}

Для удобства мы аннотировали их с помощью Lombok (синтаксический сахар для геттеров, сеттеров, конструкторов…).

Теперь заполним их:

MySubClass subclass = new MySubClass(42L, "the answer", "Verbose field not to serialize")
MyClass source = new MyClass(1L, "foo", "bar", subclass);

Наша цель — предотвратить сериализацию полей MyClass.other и MySubClass.otherVerboseInfo.

Результат, который мы ожидаем получить:

{
  "id":1,
  "name":"foo",
  "subclass":{
    "id":42,
    "description":"the answer"
  }
}

В Java:

String expectedResult = "{\"id\":1,\"name\":\"foo\",\"subclass\":{\"id\":42,\"description\":\"the answer\"}}";

3. Модификатор перехода

Мы можем пометить поле модификатором перехода:

public class MyClass {
    private long id;
    private String name;
    private transient String other;
    private MySubClass subclass;
}

public class MySubClass {
    private long id;
    private String description;
    private transient String otherVerboseInfo;
}

Сериализатор Gson будет игнорировать каждое поле, объявленное как переходное:

String jsonString = new Gson().toJson(source);
assertEquals(expectedResult, jsonString);

Хотя это очень быстро, у него также есть серьезный недостаток: каждый инструмент сериализации будет учитывать переходное состояние, а не только Gson.

Transient — это способ Java исключить из сериализации, тогда наше поле также будет отфильтровано сериализацией Serializable и каждым библиотечным инструментом или фреймворком, управляющим нашими объектами.

Кроме того, ключевое слово transient всегда работает как для сериализации, так и для десериализации, что может иметь ограничения в зависимости от вариантов использования.

4. Аннотация @Expose

Gson com.google.gson.annotations Аннотация @Expose работает наоборот.

Мы можем использовать его, чтобы объявить, какие поля сериализовать, и игнорировать остальные:

public class MyClass {
    @Expose 
    private long id;
    @Expose 
    private String name;
    private String other;
    @Expose 
    private MySubClass subclass;
}

public class MySubClass {
    @Expose 
    private long id;
    @Expose 
    private String description;
    private String otherVerboseInfo;
}   

Для этого нам нужно создать экземпляр Gson с помощью GsonBuilder:

Gson gson = new GsonBuilder()
  .excludeFieldsWithoutExposeAnnotation()
  .create();
String jsonString = gson.toJson(source);
assertEquals(expectedResult, jsonString);

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

Давайте посмотрим, как предотвратить сериализацию MyClass.other, но разрешить его заполнение во время десериализации из JSON:

@Expose(serialize = false, deserialize = true) 
private String other;

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

5. ExclusionStrategy

Решение с широкими возможностями настройки — использование стратегии com.google.gson.ExclusionStrategy.

Это позволяет нам определить (извне или с помощью анонимного внутреннего класса) стратегию, чтобы указать GsonBuilder, следует ли сериализовать поля (и/или классы) с пользовательскими критериями.

Gson gson = new GsonBuilder()
  .addSerializationExclusionStrategy(strategy)
  .create();
String jsonString = gson.toJson(source);

assertEquals(expectedResult, jsonString);

Давайте рассмотрим несколько примеров умных стратегий.

5.1. С именами классов и полей

Конечно, мы также можем жестко закодировать одно или несколько имен полей/классов: переименовать наши атрибуты.

ExclusionStrategy strategy = new ExclusionStrategy() {
    @Override
    public boolean shouldSkipField(FieldAttributes field) {
        if (field.getDeclaringClass() == MyClass.class && field.getName().equals("other")) {
            return true;
        }
        if (field.getDeclaringClass() == MySubClass.class && field.getName().equals("otherVerboseInfo")) {
            return true;
        }
        return false;
    }

    @Override
    public boolean shouldSkipClass(Class<?> clazz) {
        return false;
    }
};

5.2. С бизнес-критериями

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

В следующем примере мы идентифицируем каждое поле, начинающееся с «other», как поля, которые не следует сериализовать, независимо от того, к какому классу они принадлежат:

5.3. С пользовательской аннотацией

ExclusionStrategy strategy = new ExclusionStrategy() {
    @Override
    public boolean shouldSkipClass(Class<?> clazz) {
        return false;
    }

    @Override
    public boolean shouldSkipField(FieldAttributes field) {
        return field.getName().startsWith("other");
    }
};

Другой разумный подход заключается в создании пользовательской аннотации:

Затем мы можем использовать ExclusionStrategy, чтобы заставить ее работать точно так же, как с аннотацией @Expose, но наоборот:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Exclude {}

~~ ~ И вот стратегия:

public class MyClass {
    private long id;
    private String name;
    @Exclude 
    private String other;
    private MySubClass subclass;
}

public class MySubClass {
    private long id;
    private String description;
    @Exclude 
    private String otherVerboseInfo;
}

Этот ответ StackOverflow впервые описал эту технику.

ExclusionStrategy strategy = new ExclusionStrategy() {
    @Override
    public boolean shouldSkipClass(Class<?> clazz) {
        return false;
    }

    @Override
    public boolean shouldSkipField(FieldAttributes field) {
        return field.getAnnotation(Exclude.class) != null;
    }
};

Это позволяет нам написать аннотацию и Стратегию один раз и динамически аннотировать наши поля без дальнейших изменений.

5.4. Расширьте стратегию исключения до десериализации

Независимо от того, какую стратегию мы будем использовать, мы всегда можем контролировать, где ее следует применять.

Только при сериализации:

Только при десериализации:

Gson gson = new GsonBuilder().addSerializationExclusionStrategy(strategy)

Всегда:

Gson gson = new GsonBuilder().addDeserializationExclusionStrategy(strategy)

6. Заключение

Gson gson = new GsonBuilder().setExclusionStrategies(strategy);

Мы видели разные способы исключения полей из класса и его подклассы во время сериализации Gson.

Мы также рассмотрели основные преимущества и недостатки каждого решения.

Как всегда, полный исходный код доступен на Github.

«