Полиморфизм с gson

У меня проблема с десериализацией строки json с помощью Gson. Я получаю массив команд. Команда может быть запуском, остановкой и другим типом команды. Естественно, у меня полиморфизм, и команда запуска/остановки наследуется от команды.

Как я могу сериализовать его обратно в правильный объект команды, используя gson?

Кажется, я получаю только базовый тип, то есть объявленный тип, а не тип времени выполнения.


person Sophie    schedule 27.04.2011    source источник
comment
stackoverflow.com/q/19588020/3315914   -  person rpax    schedule 28.02.2014


Ответы (9)


Это немного поздно, но я должен был сделать то же самое сегодня. Итак, исходя из моих исследований и при использовании gson-2.0, вы действительно не хотите использовать метод registerTypeHierarchyAdapter, а скорее более приземленный метод registerTypeAdapter. И вам, конечно же, не нужно делать instanceofs или писать адаптеры для производных классов: достаточно одного адаптера для базового класса или интерфейса, если, конечно, вас устраивает стандартная сериализация производных классов. . Во всяком случае, вот код (пакет и импорт удалены) (также доступен в github):

Базовый класс (интерфейс в моем случае):

public interface IAnimal { public String sound(); }

Два производных класса Cat:

public class Cat implements IAnimal {

    public String name;

    public Cat(String name) {
        super();
        this.name = name;
    }

    @Override
    public String sound() {
        return name + " : \"meaow\"";
    };
}

И Собака:

public class Dog implements IAnimal {

    public String name;
    public int ferocity;

    public Dog(String name, int ferocity) {
        super();
        this.name = name;
        this.ferocity = ferocity;
    }

    @Override
    public String sound() {
        return name + " : \"bark\" (ferocity level:" + ferocity + ")";
    }
}

IAnimalAdapter:

public class IAnimalAdapter implements JsonSerializer<IAnimal>, JsonDeserializer<IAnimal>{

    private static final String CLASSNAME = "CLASSNAME";
    private static final String INSTANCE  = "INSTANCE";

    @Override
    public JsonElement serialize(IAnimal src, Type typeOfSrc,
            JsonSerializationContext context) {

        JsonObject retValue = new JsonObject();
        String className = src.getClass().getName();
        retValue.addProperty(CLASSNAME, className);
        JsonElement elem = context.serialize(src); 
        retValue.add(INSTANCE, elem);
        return retValue;
    }

    @Override
    public IAnimal deserialize(JsonElement json, Type typeOfT,
            JsonDeserializationContext context) throws JsonParseException  {
        JsonObject jsonObject = json.getAsJsonObject();
        JsonPrimitive prim = (JsonPrimitive) jsonObject.get(CLASSNAME);
        String className = prim.getAsString();

        Class<?> klass = null;
        try {
            klass = Class.forName(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            throw new JsonParseException(e.getMessage());
        }
        return context.deserialize(jsonObject.get(INSTANCE), klass);
    }
}

И тестовый класс:

public class Test {

    public static void main(String[] args) {
        IAnimal animals[] = new IAnimal[]{new Cat("Kitty"), new Dog("Brutus", 5)};
        Gson gsonExt = null;
        {
            GsonBuilder builder = new GsonBuilder();
            builder.registerTypeAdapter(IAnimal.class, new IAnimalAdapter());
            gsonExt = builder.create();
        }
        for (IAnimal animal : animals) {
            String animalJson = gsonExt.toJson(animal, IAnimal.class);
            System.out.println("serialized with the custom serializer:" + animalJson);
            IAnimal animal2 = gsonExt.fromJson(animalJson, IAnimal.class);
            System.out.println(animal2.sound());
        }
    }
}

Когда вы запускаете Test::main, вы получаете следующий вывод:

serialized with the custom serializer:
{"CLASSNAME":"com.synelixis.caches.viz.json.playground.plainAdapter.Cat","INSTANCE":{"name":"Kitty"}}
Kitty : "meaow"
serialized with the custom serializer:
{"CLASSNAME":"com.synelixis.caches.viz.json.playground.plainAdapter.Dog","INSTANCE":{"name":"Brutus","ferocity":5}}
Brutus : "bark" (ferocity level:5)

На самом деле я сделал вышеописанное, используя метод registerTypeHierarchyAdapter, но, похоже, это потребовало реализации пользовательских классов сериализатора/десериализатора DogAdapter и CatAdapter, которые сложно поддерживать каждый раз, когда вы хотите добавить другое поле в Dog. или к Кат.

person Marcus Junius Brutus    schedule 30.12.2011
comment
Просто небольшое дополнение: чтобы это работало, убедитесь, что ваши классы НЕ определены внутри (подкласс). - person waqaslam; 15.03.2013
comment
Обратите внимание, что сериализация имен классов и десериализация (на основе пользовательского ввода) с использованием Class.forName в некоторых ситуациях могут иметь последствия для безопасности, и поэтому команда разработчиков Gson не одобряет это. code.google.com/p/google-gson/ проблемы/подробности?id=340#c2 - person Programmer Bruce; 01.04.2013
comment
Как вам удалось не получить бесконечный цикл в сериализации, вы вызываете context.serialize(src); который будет снова вызывать ваш адаптер. Это то, что произошло в моем подобном коде. - person che javara; 28.11.2013
comment
@chejavara я думаю, что это должно быть context.serialize(src, src.getClass()); - person Dag; 28.11.2013
comment
Неправильный. Это решение не работает. Если вы вызовете context.serialize каким-либо образом, вы получите бесконечную рекурсию. Интересно, почему люди публикуют сообщения без фактического тестирования кода. Пробовал с 2.2.1. См. ошибку, описанную в stackoverflow.com/questions/13244769/. - person che javara; 28.11.2013
comment
@chejavara Решение, очевидно, было проверено, поскольку я не мог просто придумать код прямо из головы. После вашего комментария я попробовал еще раз (скопировал код из ответа и добавил только операторы импорта) и отлично работает (я протестировал его со следующей зависимостью: ‹dependency org=com.google.code.gson name=gson rev=2.0/ ›). Если ваша проблема не устранена, я думаю, я мог бы загрузить решение в репозиторий github для вас. - person Marcus Junius Brutus; 06.05.2014
comment
@MarcusJuniusBrutus это может означать, что он работает с 2.0, но не с 2.2.1 - есть ли шанс, что вы сможете повторить тест с 2.2.1? - person che javara; 07.05.2014
comment
@chejavara только что попробовал и убедился, что он работает и с 2.2.1. - person Marcus Junius Brutus; 07.05.2014
comment
@chejavara и репозиторий github, как и было обещано: github.com/mperdikeas/json-polymorphism.git - person Marcus Junius Brutus; 07.05.2014
comment
@MarcusJuniusBrutus Я запустил ваш код, и кажется, что он работает только в этом особом случае, потому что вы определили суперинтерфейс IAnimal, и IAnimalAdapter использует его. Если вместо этого у вас был только «Кот», вы получите проблему бесконечной рекурсии. Так что это решение все еще не работает в общем случае - только когда вы можете определить общий интерфейс. В моем случае не было интерфейса, поэтому мне пришлось использовать другой подход с TypeAdapterFactory. - person che javara; 07.05.2014
comment
Пользователь src.getClass().getName() вместо src.getClass().getCanonicalName(). Это означает, что код будет работать и для внутренних/вложенных классов. - person mR_fr0g; 30.01.2015
comment
Я использовал этот метод, но для предотвращения бесконечной рекурсии я создал новый экземпляр Gson без адаптера типа и использовал его для десериализации. - person Tunji_D; 01.09.2015
comment
@ mR_fr0g да, сделал это и обновил репозиторий github с образцом кода. - person Marcus Junius Brutus; 15.10.2015
comment
Это бесполезно, так как вам нужно указать тип в вызове. gsonExt.toJson(животное, IAnimal.class) Если вы вызовете gsonExt.toJson(животное), это не сработает. Это конкретно означает, что если мое животное является частью другой структуры, оно не будет правильно сериализовано. - person ModdyFire; 27.10.2017
comment
Я использую Gson 2.7, и предложение @Tunji_D работает. Используйте новый экземпляр Gson без адаптера типа для внутренней сериализации и десериализации. Это для случая, когда вам также необходимо сериализовать объект корневых классов. - person Waqas Ilyas; 22.06.2018
comment
Дополнительное примечание. Если вам нужно использовать это для нескольких классов, вы можете упростить задачу, сделав адаптер универсальным. - person Namnodorel; 20.09.2018

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

Похоже, у Gson скоро появится RuntimeTypeAdapter для более простой полиморфной десериализации. См. http://code.google.com/p/google-gson/issues/detail?id=231 для получения дополнительной информации.

Если использование нового RuntimeTypeAdapter невозможно, и вам нужно использовать Gson, то, я думаю, вам придется развернуть собственное решение, зарегистрировав пользовательский десериализатор либо как адаптер иерархии типов, либо как адаптер типов. Ниже приведен один из таких примеров.

// output:
//     Starting machine1
//     Stopping machine2

import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;

import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;

public class Foo
{
  // [{"machine_name":"machine1","command":"start"},{"machine_name":"machine2","command":"stop"}]
  static String jsonInput = "[{\"machine_name\":\"machine1\",\"command\":\"start\"},{\"machine_name\":\"machine2\",\"command\":\"stop\"}]";

  public static void main(String[] args)
  {
    GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
    CommandDeserializer deserializer = new CommandDeserializer("command");
    deserializer.registerCommand("start", Start.class);
    deserializer.registerCommand("stop", Stop.class);
    gsonBuilder.registerTypeAdapter(Command.class, deserializer);
    Gson gson = gsonBuilder.create();
    Command[] commands = gson.fromJson(jsonInput, Command[].class);
    for (Command command : commands)
    {
      command.execute();
    }
  }
}

class CommandDeserializer implements JsonDeserializer<Command>
{
  String commandElementName;
  Gson gson;
  Map<String, Class<? extends Command>> commandRegistry;

  CommandDeserializer(String commandElementName)
  {
    this.commandElementName = commandElementName;
    GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
    gson = gsonBuilder.create();
    commandRegistry = new HashMap<String, Class<? extends Command>>();
  }

  void registerCommand(String command, Class<? extends Command> commandInstanceClass)
  {
    commandRegistry.put(command, commandInstanceClass);
  }

  @Override
  public Command deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
      throws JsonParseException
  {
    try
    {
      JsonObject commandObject = json.getAsJsonObject();
      JsonElement commandTypeElement = commandObject.get(commandElementName);
      Class<? extends Command> commandInstanceClass = commandRegistry.get(commandTypeElement.getAsString());
      Command command = gson.fromJson(json, commandInstanceClass);
      return command;
    }
    catch (Exception e)
    {
      throw new RuntimeException(e);
    }
  }
}

abstract class Command
{
  String machineName;

  Command(String machineName)
  {
    this.machineName = machineName;
  }

  abstract void execute();
}

class Stop extends Command
{
  Stop(String machineName)
  {
    super(machineName);
  }

  void execute()
  {
    System.out.println("Stopping " + machineName);
  }
}

class Start extends Command
{
  Start(String machineName)
  {
    super(machineName);
  }

  void execute()
  {
    System.out.println("Starting " + machineName);
  }
}
person Programmer Bruce    schedule 08.06.2011
comment
Если вы можете изменить API, обратите внимание, что в настоящее время у Джексона есть механизм относительно простой полиморфной десериализации. Я разместил несколько примеров на странице programmerbruce.blogspot.com/2011. /05/ - person Programmer Bruce; 08.06.2011
comment
RuntimeTypeAdapter завершен, но, к сожалению, пока не похоже, что он находится в ядре Gson. :-( - person Jonathan; 14.08.2013

У Марка Юния Брута был отличный ответ (спасибо!). Чтобы расширить его пример, вы можете сделать его класс адаптера универсальным для работы со всеми типами объектов (не только IAnimal) со следующими изменениями:

class InheritanceAdapter<T> implements JsonSerializer<T>, JsonDeserializer<T>
{
....
    public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context)
....
    public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
....
}

И в тестовом классе:

public class Test {
    public static void main(String[] args) {
        ....
            builder.registerTypeAdapter(IAnimal.class, new InheritanceAdapter<IAnimal>());
        ....
}
person user2242263    schedule 11.04.2013
comment
После реализации его решения моей следующей мыслью было сделать именно это :-) - person David Levy; 07.08.2014

У GSON есть довольно хороший тестовый пример, показывающий, как определить и зарегистрировать адаптер иерархии типов.

http://code.google.com/p/google-gson/source/browse/trunk/gson/src/test/java/com/google/gson/functional/TypeHierarchyAdapterTest.java?r=739

Чтобы использовать это, сделайте следующее:

    gson = new GsonBuilder()
          .registerTypeAdapter(BaseQuestion.class, new BaseQuestionAdaptor())
          .create();

Метод сериализации адаптера может быть каскадной проверкой if-else того, какой тип он сериализует.

    JsonElement result = new JsonObject();

    if (src instanceof SliderQuestion) {
        result = context.serialize(src, SliderQuestion.class);
    }
    else if (src instanceof TextQuestion) {
        result = context.serialize(src, TextQuestion.class);
    }
    else if (src instanceof ChoiceQuestion) {
        result = context.serialize(src, ChoiceQuestion.class);
    }

    return result;

Десериализация немного хакерская. В примере модульного теста он проверяет наличие контрольных атрибутов, чтобы решить, в какой класс десериализоваться. Если вы можете изменить источник объекта, который вы сериализуете, вы можете добавить атрибут classType к каждому экземпляру, который содержит полное доменное имя имени класса экземпляра. Однако это очень необъектно-ориентировано.

person k.c. sham    schedule 03.11.2011

Компания Google выпустила собственный RuntimeTypeAdapterFactory для обработки полиморфизма, но, к сожалению, он не является частью ядра gson (вы должны скопировать и вставить класс в свой проект).

Пример:

RuntimeTypeAdapterFactory<Animal> runtimeTypeAdapterFactory = RuntimeTypeAdapterFactory
.of(Animal.class, "type")
.registerSubtype(Dog.class, "dog")
.registerSubtype(Cat.class, "cat");

Gson gson = new GsonBuilder()
    .registerTypeAdapterFactory(runtimeTypeAdapterFactory)
    .create();

Здесь я разместил полный рабочий пример этого с использованием моделей Animal, Dog и Cat.

Я думаю, что лучше положиться на этот адаптер, чем переделывать его с нуля.

person db80    schedule 15.11.2017
comment
Для тех, кто хочет с ним поиграться — он есть в maven под org.danilopianini:gson-extras. Плохо то, что он не работает с классами данных Kotlin. - person expert; 23.10.2020

Прошло много времени, но я не мог найти действительно хорошее решение в Интернете. Вот небольшой поворот решения @MarcusJuniusBrutus, который позволяет избежать бесконечной рекурсии.

Сохраните тот же десериализатор, но удалите сериализатор -

public class IAnimalAdapter implements JsonDeSerializer<IAnimal> {
  private static final String CLASSNAME = "CLASSNAME";
  private static final String INSTANCE  = "INSTANCE";

  @Override
  public IAnimal deserialize(JsonElement json, Type typeOfT,
        JsonDeserializationContext context) throws JsonParseException  {
    JsonObject jsonObject =  json.getAsJsonObject();
    JsonPrimitive prim = (JsonPrimitive) jsonObject.get(CLASSNAME);
    String className = prim.getAsString();

    Class<?> klass = null;
    try {
        klass = Class.forName(className);
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
        throw new JsonParseException(e.getMessage());
    }
    return context.deserialize(jsonObject.get(INSTANCE), klass);
  }
}

Затем в исходном классе добавьте поле с @SerializedName("CLASSNAME"). Хитрость заключается в том, чтобы инициализировать это в конструкторе базового класса, поэтому сделайте свой интерфейс абстрактным классом.

public abstract class IAnimal {
  @SerializedName("CLASSNAME")
  public String className;

  public IAnimal(...) {
    ...
    className = this.getClass().getName();
  }
}

Причина, по которой здесь нет бесконечной рекурсии, заключается в том, что мы передаем фактический класс среды выполнения (т. е. Dog, а не IAnimal) в context.deserialize. Это не будет вызывать наш адаптер типа, пока мы используем registerTypeAdapter, а не registerTypeHierarchyAdapter

person Ginandi    schedule 22.10.2015

Обновленный ответ - лучшие части всех других ответов

Я описываю решения для различных вариантов использования, а также рассматриваю проблему бесконечной рекурсии.

  • Случай 1: Вы контролируете классы, то есть вы можете писать свои собственные классы Cat, Dog, а также интерфейс IAnimal. Вы можете просто следовать решению, предоставленному @marcus-junius-brutus (ответ с самым высоким рейтингом)

    Не будет бесконечной рекурсии, если есть общий базовый интерфейс как IAnimal

    Но что, если я не хочу реализовывать IAnimal или любой подобный интерфейс?

    Затем @marcus-junius-brutus (ответ с самым высоким рейтингом) приведет к ошибке бесконечной рекурсии. В этом случае мы можем сделать что-то вроде ниже.

    Нам пришлось бы создать конструктор копирования внутри базового класса и подкласса-оболочки следующим образом:

.

// Base class(modified)
public class Cat implements IAnimal {

    public String name;

    public Cat(String name) {
        super();
        this.name = name;
    }
    // COPY CONSTRUCTOR
    public Cat(Cat cat) {
        this.name = cat.name;
    }

    @Override
    public String sound() {
        return name + " : \"meaow\"";
    };
}



    // The wrapper subclass for serialization
public class CatWrapper extends Cat{


    public CatWrapper(String name) {
        super(name);
    }

    public CatWrapper(Cat cat) {
        super(cat);
    }
}

И сериализатор для типа Cat:

public class CatSerializer implements JsonSerializer<Cat> {

    @Override
    public JsonElement serialize(Cat src, Type typeOfSrc, JsonSerializationContext context) {

        // Essentially the same as the type Cat
        JsonElement catWrapped = context.serialize(new CatWrapper(src));

        // Here, we can customize the generated JSON from the wrapper as we want.
        // We can add a field, remove a field, etc.


        return modifyJSON(catWrapped);
    }

    private JsonElement modifyJSON(JsonElement base){
        // TODO: Modify something
        return base;
    }
}

Итак, зачем конструктор копирования?

Ну, как только вы определите конструктор копирования, независимо от того, насколько сильно изменится базовый класс, ваша оболочка будет продолжать играть ту же роль. Во-вторых, если мы не определим конструктор копирования и просто создадим подкласс базового класса, тогда нам придется «говорить» в терминах расширенного класса, то есть CatWrapper. Вполне возможно, что ваши компоненты говорят о базовом классе, а не о типе оболочки.

Есть ли простая альтернатива?

Конечно, теперь это было представлено Google — это реализация RuntimeTypeAdapterFactory:

RuntimeTypeAdapterFactory<Animal> runtimeTypeAdapterFactory = RuntimeTypeAdapterFactory
.of(Animal.class, "type")
.registerSubtype(Dog.class, "dog")
.registerSubtype(Cat.class, "cat");

Gson gson = new GsonBuilder()
    .registerTypeAdapterFactory(runtimeTypeAdapterFactory)
    .create();

Здесь вам нужно будет ввести поле под названием «тип» в Animal и значение того же внутри Dog как «собака», Cat как «кошка».

Полный пример: https://static.javadoc.io/org.danilopianini/gson-extras/0.2.1/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.html

  • Случай 2: Вы не контролируете классы. Вы присоединяетесь к компании или используете библиотеку, в которой классы уже определены, и ваш менеджер не хочет, чтобы вы их каким-либо образом изменяли. Вы можете создавать подклассы своих классов и реализовывать общий интерфейс маркеров (у которого нет никаких методов). ), например AnimalInterface.

    Ex:

.

// The class we are NOT allowed to modify

public class Dog implements IAnimal {

    public String name;
    public int ferocity;

    public Dog(String name, int ferocity) {
        super();
        this.name = name;
        this.ferocity = ferocity;
    }

    @Override
    public String sound() {
        return name + " : \"bark\" (ferocity level:" + ferocity + ")";
    }
}


// The marker interface

public interface AnimalInterface {
}

// The subclass for serialization

public class DogWrapper  extends Dog implements AnimalInterface{

    public DogWrapper(String name, int ferocity) {
        super(name, ferocity);
    }

}

// The subclass for serialization

public class CatWrapper extends Cat implements AnimalInterface{


    public CatWrapper(String name) {
        super(name);
    }
}

Таким образом, мы будем использовать CatWrapper вместо Cat, DogWrapper вместо Dog и AlternativeAnimalAdapter вместо IAnimalAdapter.

// The only difference between `IAnimalAdapter` and `AlternativeAnimalAdapter` is that of the interface, i.e, `AnimalInterface` instead of `IAnimal`

public class AlternativeAnimalAdapter implements JsonSerializer<AnimalInterface>, JsonDeserializer<AnimalInterface> {

    private static final String CLASSNAME = "CLASSNAME";
    private static final String INSTANCE  = "INSTANCE";

    @Override
    public JsonElement serialize(AnimalInterface src, Type typeOfSrc,
                                 JsonSerializationContext context) {

        JsonObject retValue = new JsonObject();
        String className = src.getClass().getName();
        retValue.addProperty(CLASSNAME, className);
        JsonElement elem = context.serialize(src); 
        retValue.add(INSTANCE, elem);
        return retValue;
    }

    @Override
    public AnimalInterface deserialize(JsonElement json, Type typeOfT,
            JsonDeserializationContext context) throws JsonParseException  {
        JsonObject jsonObject = json.getAsJsonObject();
        JsonPrimitive prim = (JsonPrimitive) jsonObject.get(CLASSNAME);
        String className = prim.getAsString();

        Class<?> klass = null;
        try {
            klass = Class.forName(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            throw new JsonParseException(e.getMessage());
        }
        return context.deserialize(jsonObject.get(INSTANCE), klass);
    }
}

Выполняем тест:

public class Test {

    public static void main(String[] args) {

        // Note that we are using the extended classes instead of the base ones
        IAnimal animals[] = new IAnimal[]{new CatWrapper("Kitty"), new DogWrapper("Brutus", 5)};
        Gson gsonExt = null;
        {
            GsonBuilder builder = new GsonBuilder();
            builder.registerTypeAdapter(AnimalInterface.class, new AlternativeAnimalAdapter());
            gsonExt = builder.create();
        }
        for (IAnimal animal : animals) {
            String animalJson = gsonExt.toJson(animal, AnimalInterface.class);
            System.out.println("serialized with the custom serializer:" + animalJson);
            AnimalInterface animal2 = gsonExt.fromJson(animalJson, AnimalInterface.class);
        }
    }
}

Вывод:

serialized with the custom serializer:{"CLASSNAME":"com.examples_so.CatWrapper","INSTANCE":{"name":"Kitty"}}
serialized with the custom serializer:{"CLASSNAME":"com.examples_so.DogWrapper","INSTANCE":{"name":"Brutus","ferocity":5}}
person Manish Kumar Sharma    schedule 15.08.2019

Если вы хотите управлять TypeAdapter для типа и другим для его подтипа, вы можете использовать TypeAdapterFactory следующим образом:

public class InheritanceTypeAdapterFactory implements TypeAdapterFactory {

    private Map<Class<?>, TypeAdapter<?>> adapters = new LinkedHashMap<>();

    {
        adapters.put(Animal.class, new AnimalTypeAdapter());
        adapters.put(Dog.class, new DogTypeAdapter());
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
        TypeAdapter<T> typeAdapter = null;
        Class<?> currentType = Object.class;
        for (Class<?> type : adapters.keySet()) {
            if (type.isAssignableFrom(typeToken.getRawType())) {
                if (currentType.isAssignableFrom(type)) {
                    currentType = type;
                    typeAdapter = (TypeAdapter<T>)adapters.get(type);
                }
            }
        }
        return typeAdapter;
    }
}

Эта фабрика отправит наиболее точный TypeAdapter

person r3n0j    schedule 23.11.2016

Если вы объедините ответ Маркуса Юниуса Брутуса с правкой пользователя 2242263, вы можете избежать необходимости указывать большую иерархию классов в своем адаптере, определив свой адаптер как работающий с типом интерфейса. Затем вы можете предоставить реализации toJSON() и fromJSON() по умолчанию в своем интерфейсе (который включает только эти два метода) и иметь каждый класс, который вам нужен для сериализации, реализующий ваш интерфейс. Чтобы иметь дело с приведением, в своих подклассах вы можете предоставить статический метод fromJSON(), который десериализует и выполняет соответствующее приведение из вашего типа интерфейса. Это отлично сработало для меня (только будьте осторожны с сериализацией/десериализацией классов, содержащих хэш-карты, — добавьте это при создании экземпляра вашего компоновщика gson:

GsonBuilder builder = new GsonBuilder().enableComplexMapKeySerialization();

Надеюсь, это поможет кому-то сэкономить время и силы!

person sodapoparooni    schedule 25.09.2019