Изключете полетата от сериализацията в 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. Преходен модификатор

Можем да маркираме поле с преходния модификатор:

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.

Преходният е Java начинът да се изключи от сериализация, тогава нашето поле също ще бъде филтрирано от сериализацията на Serializable и от всеки библиотечен инструмент или рамка, управляващи нашите обекти.

Освен това преходната ключова дума винаги работи както за сериализация, така и за десериализация, което може да бъде ограничаващо в зависимост от случаите на употреба.

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. Изключване Стратегия .

Това ни позволява да дефинираме (външно или с 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. С бизнес критерии

Тъй като просто трябва да върнем булева стойност, можем да внедрим всяка бизнес логика, която ни харесва, в този метод.

В следващия пример ще идентифицираме всяко поле, започващо с „друго“ като полета, които не трябва да бъдат сериализирани, независимо от класа, към който принадлежат:

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.