Статьи

JSON с GSON и абстрактными классами

Я перешел на Google Gson после многих лет использования библиотеки org.json для поддержки формата обмена данными JSON в Java. org.json — это библиотека более низкого уровня, поэтому вам нужно создать JSONObject, JSONArray, JSONString и т. д. и выполнить другую работу низкого уровня. Gson упрощает эту работу. Он предоставляет простые методы toJson () и fromJson () для преобразования произвольных объектов Java в JSON и наоборот, поддерживает универсальные Java-функции, позволяет настраивать представления для объектов, генерирует компактные и удобочитаемые выходные данные JSON и обладает многими другими полезностями. Я люблю это все больше и больше. Использование просто. Предположим, у нас есть класс с именем Circle.

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 Олега Вараксина в блоге Мысли о разработке программного обеспечения .