Десериализация абстрактного класса в Gson - PullRequest
36 голосов
/ 02 сентября 2010

У меня есть объект дерева в формате JSON, который я пытаюсь десериализовать с помощью Gson.Каждый узел содержит свои дочерние узлы как поля типа объекта Node.Node - это интерфейс, который имеет несколько конкретных реализаций классов.В процессе десериализации, как я могу сообщить Gson, какой конкретный класс реализовать при десериализации узла, если я не знаю a priori, к какому типу принадлежит узел?Каждый узел имеет поле члена, определяющее тип.Есть ли способ получить доступ к полю, когда объект находится в сериализованной форме, и каким-то образом передать тип Gson?

Спасибо!

Ответы [ 4 ]

44 голосов
/ 02 сентября 2010

Я бы предложил добавить пользовательский JsonDeserializer для Node s:

Gson gson = new GsonBuilder()
    .registerTypeAdapter(Node.class, new NodeDeserializer())
    .create();

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

26 голосов
/ 02 февраля 2012

Вам необходимо зарегистрировать JSONSerializer и JSONDeserializer. Также вы можете реализовать универсальный адаптер для всех ваших интерфейсов следующим образом:

  • Во время сериализации: добавьте META-информацию фактического типа класса impl.
  • Во время десериализации: получить эту метаинформацию и вызвать JSONDeserailize этого класса

Вот реализация, которую я использовал для себя и отлично работает.

public class PropertyBasedInterfaceMarshal implements
        JsonSerializer<Object>, JsonDeserializer<Object> {

    private static final String CLASS_META_KEY = "CLASS_META_KEY";

    @Override
    public Object deserialize(JsonElement jsonElement, Type type,
            JsonDeserializationContext jsonDeserializationContext)
            throws JsonParseException {
        JsonObject jsonObj = jsonElement.getAsJsonObject();
        String className = jsonObj.get(CLASS_META_KEY).getAsString();
        try {
            Class<?> clz = Class.forName(className);
            return jsonDeserializationContext.deserialize(jsonElement, clz);
        } catch (ClassNotFoundException e) {
            throw new JsonParseException(e);
        }
    }

    @Override
    public JsonElement serialize(Object object, Type type,
            JsonSerializationContext jsonSerializationContext) {
        JsonElement jsonEle = jsonSerializationContext.serialize(object, object.getClass());
        jsonEle.getAsJsonObject().addProperty(CLASS_META_KEY,
                object.getClass().getCanonicalName());
        return jsonEle;
    }

}

Тогда вы можете зарегистрировать этот адаптер для всех ваших интерфейсов следующим образом

Gson gson = new GsonBuilder()
        .registerTypeAdapter(IInterfaceOne.class,
                new PropertyBasedInterfaceMarshal())
        .registerTypeAdapter(IInterfaceTwo.class,
                new PropertyBasedInterfaceMarshal()).create();
1 голос
/ 21 февраля 2012

Насколько я могу судить, это не работает для не-коллекционных типов, или, более конкретно, в ситуациях, когда конкретный тип используется для сериализации, а тип интерфейса используется для десериализации. То есть, если у вас есть простой класс, реализующий интерфейс, и вы сериализуете конкретный класс, а затем задаете интерфейс для десериализации, вы окажетесь в неисправимой ситуации.

В приведенном выше примере адаптер типа зарегистрирован для интерфейса, но при сериализации с использованием конкретного класса он не будет использоваться, то есть данные CLASS_META_KEY никогда не будут установлены.

Если вы укажете адаптер в качестве иерархического адаптера (тем самым указав gson использовать его для всех типов в иерархии), вы окажетесь в бесконечном цикле, поскольку сериализатор будет просто вызывать себя.

Кто-нибудь знает, как сериализовать из конкретной реализации интерфейса, а затем десериализовать, используя только интерфейс и InstanceCreator?

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

Проблема регистрируется здесь:

http://code.google.com/p/google-gson/issues/detail?id=411&q=interface

0 голосов
/ 22 февраля 2013

Вы должны использовать TypeToken класс из Google Gson. Конечно, вам понадобится универсальный класс T, чтобы он работал

Type fooType = new TypeToken<Foo<Bar>>() {}.getType();

gson.toJson (foo, fooType);

gson.fromJson(json, fooType);
...