Эффективно обрабатывать все виды ответов JSON в Swift

Как некоторые из вас, возможно, уже знают, Decodable — это протокол, используемый для декодирования типов из внешних представлений (таких как JSON и списки свойств), который был выпущен в Swift 4.

Согласно Apple, Decodable это:

Тип, который может декодировать себя из внешнего представления.

Итак, допустим, нам дан JSON вот так:

[
    {
        "type": "car",
        "identifier": "12345",
        "model": "Audi"
    }
]

Мы можем определить тип Car, который соответствует Decodable, и этого должно быть достаточно для его декодирования с использованием экземпляра JSONDecoder. Вы можете попробовать следующий код на игровой площадке Swift:

Теперь предположим, что серверная команда решает также включить Motorcycles в тот же ответ:

[
    {
        "type": "car",
        "identifier": "12345",
        "model": "Audi"
    },
    {
        "type": "Motorcyle",
        "brand": "Yamaha"
    }
]

В идеальном мире у нас должно быть два разных ключа: один для Cars и один для Motorcycle. Но, несмотря на то, что это не так распространено (или, по крайней мере, не должно), иногда мы можем получить плохо оформленный ответ JSON.

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

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

Обычная реализация перечисления Either будет выглядеть так:

Чтобы Either работал с Decodable, нам нужно немного расширить его функциональные возможности. Если мы попытаемся напрямую декодировать ответ JSON, используя что-то вроде Either<Car, Motorcycle>, компилятор выдаст ошибку, потому что Either должен соответствовать протоколу Decodable.

Учитывая, что перечисление не может содержать никаких сохраненных свойств, ключи кодирования не могут быть синтезированы для Either. Из-за этого также потребуется реализовать инициализатор init(from: Decoder):

Теперь мы должны быть готовы использовать наше перечисление Either для разбора ответа JSON:

И это должно быть так! При этом вы сможете получить Cars или Motorcycles из ответа JSON.

В этот момент вы можете спросить: что произойдет, если мы получим дополнительное транспортное средство? Что, если мы получим два дополнительных? Или, может быть, больше?

Как вы могли догадаться, Either в этом сценарии работать не будет. Лучшим подходом было бы использование стирания типа для определения типа AnyDecodable.

Я раскрою эту тему в следующей статье. Вы можете найти полный пример кода в этом репозитории GitHub: