1
2
3
4
5
6
7
8
9
|
public class Circle { private int radius = 10 ; private String backgroundColor = "#FF0000" ; private String borderColor = "#000000" ; private double scaleFactor = 0.5 ; ... // getter / setter } |
Сериализация (объект Java -> JSON) может быть выполнена следующим образом:
01
02
03
04
05
06
07
08
09
10
11
|
Circle circle = new Circle(); Gson gson = new Gson(); String json = gson.toJson(circle); ==> json is { "radius" : 10 , "backgroundColor" : "#FF0000" , "borderColor" : "#000000" , "scaleFactor" : 0.5 , ... } |
Десериализация (JSON -> объект Java) — это всего лишь одна строка кода:
1
2
|
Circle circle2 = gson.fromJson(json, Circle. class ); ==> circle2 is the same as the circle above |
Все работает как шарм. Есть только одна проблема, с которой я столкнулся с абстрактными классами. Предположим, у нас есть абстрактный класс AbstractElement и многие другие классы, расширяющие этот
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
public abstract class AbstractElement { private String uuid; // getter / setter } public class Circle extends AbstractElement { ... } public class Rectangle extends AbstractElement { ... } public class Ellipse extends AbstractElement { ... } |
Предположим теперь, что мы храним все конкретные классы в списке или карте, параметризованной с помощью AbstractElement
1
2
3
4
5
6
|
public class Whiteboard { private Map<String, AbstractElement> elements = new LinkedHashMap<String, AbstractElement>(); ... } |
Проблема в том, что конкретный класс не раскрывается при десериализации. Это неизвестно в JSON-представлении Whiteboard. Как создать подходящий Java-класс из JSON-представления и поместить в элементы Map <String, AbstractElement>? Я ничего не нашел в документации, что бы решить эту проблему. Очевидно, что нам нужно хранить метаинформацию в представлениях JSON о конкретных классах. Это точно. Gson позволяет регистрировать ваши собственные сериализаторы и десериализаторы. Это мощная особенность Gson. Иногда представление по умолчанию не то, что вы хотите. Это часто имеет место, например, при работе со сторонними библиотечными классами. Существует достаточно примеров написания пользовательских сериализаторов / десериализаторов. Я собираюсь создать класс адаптера, реализующий оба интерфейса JsonSerializer, JsonDeserializer и зарегистрировать его для моего абстрактного класса AbstractElement.
1
2
3
|
GsonBuilder gsonBilder = new GsonBuilder(); gsonBilder.registerTypeAdapter(AbstractElement. class , new AbstractElementAdapter()); Gson gson = gsonBilder.create(); |
А вот и AbstractElementAdapter:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
package com.googlecode.whiteboard.json; import com.google.gson.*; import com.googlecode.whiteboard.model.base.AbstractElement; import java.lang.reflect.Type; public class AbstractElementAdapter implements JsonSerializer<AbstractElement>, JsonDeserializer<AbstractElement> { @Override public JsonElement serialize(AbstractElement src, Type typeOfSrc, JsonSerializationContext context) { JsonObject result = new JsonObject(); result.add( "type" , new JsonPrimitive(src.getClass().getSimpleName())); result.add( "properties" , context.serialize(src, src.getClass())); return result; } @Override public AbstractElement deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { JsonObject jsonObject = json.getAsJsonObject(); String type = jsonObject.get( "type" ).getAsString(); JsonElement element = jsonObject.get( "properties" ); try { return context.deserialize(element, Class.forName( "com.googlecode.whiteboard.model." + type)); } catch (ClassNotFoundException cnfe) { throw new JsonParseException( "Unknown element type: " + type, cnfe); } } } |
Я добавляю два свойства JSON: одно — «тип», а другое — «свойства». Первое свойство содержит конкретный класс реализации (простое имя) AbstractElement, а второе — сам сериализованный объект. JSON выглядит как
01
02
03
04
05
06
07
08
09
10
|
{ "type" : "Circle" , "properties" : { "radius" : 10 , "backgroundColor" : "#FF0000" , "borderColor" : "#000000" , "scaleFactor" : 0.5 , ... } } |
Мы извлекаем выгоду из свойства «type» во время десериализации. Конкретный класс теперь можно создать с помощью Class.forName («com.googlecode.whiteboard.model. + Type»), где «com.googlecode.whiteboard.model». + type — это полное имя класса. Следующий звонок
1
|
public <T> T deserialize(JsonElement json, Type typeOfT) throws JsonParseException |
from JsonDeserializationContext вызывает десериализацию по умолчанию для указанного объекта и завершает задание.
Ссылка: JSON с GSON и абстрактные классы от нашего партнера по JCG Олега Вараксина в блоге Мысли о разработке программного обеспечения .