Перейти к основному содержимому

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

· 5 мин. чтения

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Всегда:

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

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

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

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

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