Недавно я столкнулся с ситуацией, когда RESTful API предоставил мне список объектов. Обычно это не проблема, поскольку REST предоставляет вам структуру, которую легко преобразовать. Подвох заключался в том, что некоторые объекты внутри других объектов были полиморфными и требовали парсинга определенных полей.

Предыдущее решение состояло в том, чтобы иметь HashMap, содержащую все предоставленные поля. Затем приложению нужно было проверить, какой тип объекта был возвращен, и решить, заполнены ли все необходимые пары «ключ-значение» пригодными для использования данными. Это не было предпочтительным решением и подвержено ошибкам.

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

Пример проблемы

Возьмем, к примеру, следующую JSON-структуру:

Как видите, объекты в массиве body содержат поле с именем type, которое определяет тип возвращаемого объекта. Это даст вам гибкость для анализа объектов в их собственных уважаемых моделях, которые в данном случае должны быть «text», «image» и «video». См. 3 примера моделей ниже с использованием Google AutoValue.

(Для краткости я не упомянул генераторы Builders и Gson Type Adapter в приведенных выше примерах.)

Решение

Сначала нам нужно создать саму модель статьи. В приведенном ниже примере вы заметите возвращаемый тип List<Block>. Если вы посмотрите на пример кода Blocks, вы увидите, что все они реализуют интерфейс Block. Таким образом, мы можем легко определить новые типизированные объекты, которые мы хотим поддерживать в будущих обновлениях.

Но теперь разбор его; здесь вмешается магия под названием RuntimeTypeAdapterFactory! При создании парсера Gson вы можете регистрировать всевозможные фабрики адаптеров типов. Добавьте сгенерированный адаптер AutoValue и адаптер среды выполнения, и все готово.

Например:

Всякий раз, когда синтаксический анализатор Gson встречает объект типа Block, адаптер проверяет, может ли он выполнить синтаксический анализ любого из определенных подтипов. Если ваш API определяет тип в поле, отличном от "type", вы можете указать его. "type" — это имя ключа по умолчанию, поэтому вы можете полностью его опустить, если ваш API работает таким образом.

Предостережения

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

  1. Если синтаксический анализатор обнаружит непредвиденный тип, будет выдано исключение, и весь ваш объект будет недействителен. Вы можете изменить синтаксический анализатор, чтобы вместо этого он возвращал null, если вам не нравится такой подход.
  2. Проанализированное поле "type" не будет возвращено в модель — если вам нужна эта информация, вам нужно будет добавить ее повторно.

Удачного кодирования!