У меня проблема с десериализацией строки json с помощью Gson. Я получаю массив команд. Команда может быть запуском, остановкой или другим типом команды. Естественно у меня есть полиморфизм, и команда запуска / остановки наследуется от команды.
Как я могу сериализовать его обратно в правильный командный объект с помощью gson?
Кажется, я получаю только базовый тип, то есть объявленный тип, а не тип времени выполнения.
Ответы:
Это немного поздно, но сегодня мне пришлось сделать то же самое. Итак, на основе моих исследований и при использовании gson-2.0 вы действительно не хотите использовать метод registerTypeHierarchyAdapter , а скорее более приземленный метод registerTypeAdapter . И вам, конечно, не нужно создавать instanceofs или писать адаптеры для производных классов: только один адаптер для базового класса или интерфейса, если, конечно, вас устраивает сериализация по умолчанию для производных классов. Во всяком случае, вот код (пакет и импорт удалены) (также доступен в github ):
Базовый класс (в моем случае интерфейс):
Два производных класса Cat:
И собака:
IAnimalAdapter:
И класс Test:
Когда вы запустите Test :: main, вы получите следующий результат:
Я действительно сделал это, используя метод registerTypeHierarchyAdapter , но для этого, похоже, потребовалось реализовать собственные классы сериализатора / десериализатора DogAdapter и CatAdapter, которые сложно поддерживать в любое время, когда вы хотите добавить другое поле в Dog или в Cat.
источник
В настоящее время Gson имеет механизм регистрации адаптера иерархии типов. который, как сообщается, может быть настроен для простой полиморфной десериализации, но я не понимаю, как это происходит, поскольку адаптер иерархии типов выглядит просто как комбинированный сериализатор / десериализатор / создатель экземпляра, оставляя детали создания экземпляра на усмотрение кодировщика, без предоставления фактической регистрации полиморфного типа.
Похоже, что у Gson скоро появится
RuntimeTypeAdapter
более простая полиморфная десериализация. См. Http://code.google.com/p/google-gson/issues/detail?id=231. для получения дополнительной информации.Если использовать новый
RuntimeTypeAdapter
невозможно и вам нужно использовать Gson, я думаю, вам придется развернуть свое собственное решение, зарегистрировав собственный десериализатор либо как адаптер иерархии типов, либо как адаптер типа. Ниже приводится один из таких примеров.источник
RuntimeTypeAdapter
теперь завершено, к сожалению, пока не похоже, что оно в ядре Gson. :-(У Марка Юния Брута был отличный ответ (спасибо!). Чтобы расширить его пример, вы можете сделать его класс адаптера универсальным для работы со всеми типами объектов (не только IAnimal) со следующими изменениями:
И в тестовом классе:
источник
GSON предлагает здесь довольно хороший тестовый пример, показывающий, как определить и зарегистрировать адаптер иерархии типов.
http://code.google.com/p/google-gson/source/browse/trunk/gson/src/test/java/com/google/gson/functional/TypeHierarchyAdapterTest.java?r=739
Чтобы использовать это, сделайте следующее:
Метод Serialize адаптера может быть каскадной проверкой if-else того, какого типа он сериализует.
Десериализация - это немного хакерский процесс. В примере модульного теста он проверяет наличие контрольных атрибутов, чтобы решить, в какой класс десериализоваться. Если вы можете изменить источник сериализуемого объекта, вы можете добавить атрибут classType к каждому экземпляру, который содержит FQN имени класса экземпляра. Хотя это очень не объектно-ориентированный подход.
источник
Google выпустила собственный RuntimeTypeAdapterFactory для обработки полиморфизма, но, к сожалению, он не является частью ядра gson (вы должны скопировать и вставить класс в свой проект).
Пример:
Здесь я разместил полный рабочий пример с использованием моделей Animal, Dog и Cat.
Думаю, лучше положиться на этот адаптер, чем заново реализовывать его с нуля.
источник
Прошло много времени, но я не мог найти действительно хорошего решения в Интернете ... Вот небольшой поворот в решении @ MarcusJuniusBrutus, который позволяет избежать бесконечной рекурсии.
Оставьте тот же десериализатор, но удалите сериализатор -
Затем в исходном классе добавьте поле с расширением
@SerializedName("CLASSNAME")
. Хитрость заключается в том, чтобы инициализировать это в конструкторе базового класса , поэтому превратите ваш интерфейс в абстрактный класс.Причина, по которой здесь нет бесконечной рекурсии, заключается в том, что мы передаем фактический класс среды выполнения (т.е. Dog, а не IAnimal)
context.deserialize
. Это не вызовет наш адаптер типа, пока мы используем,registerTypeAdapter
а неregisterTypeHierarchyAdapter
источник
Обновленный ответ - Лучшие части всех остальных ответов
Я описываю решение для различных случаев применения и буду решение бесконечной рекурсии проблемы , а также
Случай 1: Вы контролируете классы , то есть, вы можете написать свои собственные
Cat
,Dog
классы, а такжеIAnimal
интерфейс. Вы можете просто следовать решению, предоставленному @ marcus-junius-brutus (самый популярный ответ)Бесконечной рекурсии не будет, если есть общий базовый интерфейс как
IAnimal
Но что, если я не хочу реализовывать тот
IAnimal
или иной интерфейс?Тогда @ marcus-junius-brutus (ответ с самым высоким рейтингом) вызовет бесконечную ошибку рекурсии. В этом случае мы можем сделать что-то вроде ниже.
Нам нужно будет создать конструктор копирования внутри базового класса и подкласса оболочки следующим образом:
.
И сериализатор для типа
Cat
:Итак, почему конструктор копирования?
Что ж, после того как вы определите конструктор копирования, независимо от того, насколько сильно изменится базовый класс, ваша оболочка продолжит выполнять ту же роль. Во-вторых, если мы не определяем конструктор копирования и просто подклассифицируем базовый класс, тогда нам придется «говорить» в терминах расширенного класса, т. Е.
CatWrapper
. Вполне возможно, что ваши компоненты говорят о базовом классе, а не о типе оболочки.Есть ли простая альтернатива?
Конечно, теперь он был представлен Google - это
RuntimeTypeAdapterFactory
реализация:Здесь вам нужно будет ввести поле с именем «type» in
Animal
и значение того же внутри,Dog
чтобы быть «dog»,Cat
чтобы быть «cat».Полный пример: https://static.javadoc.io/org.danilopianini/gson-extras/0.2.1/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.html
Случай 2: Вы не контролируете классы . Вы присоединяетесь к компании или используете библиотеку, в которой классы уже определены, и ваш менеджер не хочет, чтобы вы меняли их каким-либо образом - вы можете создать подклассы для своих классов и заставить их реализовать общий интерфейс маркера (который не имеет никаких методов ), например
AnimalInterface
.Пример:
.
Итак, мы бы использовали
CatWrapper
вместоCat
,DogWrapper
вместоDog
иAlternativeAnimalAdapter
вместоIAnimalAdapter
Выполняем тест:
Вывод:
источник
Если вы хотите управлять TypeAdapter для типа и другим для его подтипа, вы можете использовать TypeAdapterFactory следующим образом:
Эта фабрика пришлет самый точный TypeAdapter
источник
Если вы объедините ответ Маркуса Юниуса Брута с редактированием user2242263, вы можете избежать необходимости указывать большую иерархию классов в своем адаптере, определив свой адаптер как работающий с типом интерфейса. Затем вы можете предоставить реализации по умолчанию для toJSON () и fromJSON () в своем интерфейсе (который включает только эти два метода) и иметь каждый класс, который вам нужно для сериализации, для реализации вашего интерфейса. Чтобы иметь дело с приведением типов, в своих подклассах вы можете предоставить статический метод fromJSON (), который десериализует и выполняет соответствующее приведение из вашего типа интерфейса. Это отлично сработало для меня (просто будьте осторожны с сериализацией / десериализацией классов, содержащих хэш-карты - добавьте это, когда вы создаете экземпляр своего построителя gson:
GsonBuilder builder = new GsonBuilder().enableComplexMapKeySerialization();
Надеюсь, это поможет кому-то сэкономить время и силы!
источник