Исключить поля из сериализации в Gson

Исключить поля из сериализации в Gson

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. Переходный модификатор

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

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

Хотя это очень быстро, но есть и серьезный недостаток:every serialization tool will take transient into account, а не только Gson.

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

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

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

Аннотация Gsoncom.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*.

Это позволяет нам определить (внешне или с помощью Anonimous Inner Class) стратегию, чтобы инструктировать 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», как поля, которые не следует сериализовать, независимо от того, к какому классу они принадлежат:

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

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

5.3. С настраиваемой аннотацией

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

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

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

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

А вот и стратегия:

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

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

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

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

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

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

Только во время сериализации:

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

Только во время десериализации:

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

Всегда:

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

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

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

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

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