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

По моему опыту работы с одним из таких API-интерфейсов, я смоделировал одно из свойств объекта JSON в виде перечисления (с двумя случаями).

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

Основная цель этой статьи - показать, как безопасно декодировать перечисления при работе с REST API. Таким образом, если возникнет четвертый неожиданный случай, код будет enum-proof 😎.

Предпосылка:

  • Некоторый опыт работы с REST API
  • Некоторое понимание протоколов быстрого кодирования и декодирования

Сценарий

Предположим, у вас есть список данных транзакции, возвращаемых REST API. Каждая транзакция имеет тип, который может быть дебетовым или кредитным.

Хотите прочитать эту историю позже? Сохраните в Журнале.

Чтобы декодировать этот объект json, нам нужно настроить то, каким будет наше ожидаемое представление в swift.

Примечательно, что протокол Codable используется в примере кода для включения кодирования данных. Однако озабоченность вызывает поведение транзакции типа , представленной ниже:

Все хорошо, правда? Неправильный…

Декодер выдает следующую ошибку:
Cannot initialize TransactionType from invalid String value refund

Что пошло не так ?

Перечисление TransactionType определяется как тип String. Поскольку компилятор Swift обеспечивает автоматическое соответствие RawRepresentable, вы можете создать TransactionType с помощью init?(rawValue:)initializer. Этот инициализатор необработанного значения не работает.

Неудачный инициализатор - это тип инициализатора, который создает необязательный экземпляр или неявно развернутый необязательный экземпляр типа, для которого объявлен инициализатор. В результате неудачный инициализатор может вернуть nil, чтобы указать, что инициализация не удалась.

let refundType = Transaction(rawValue: "refund")
//refundType = nil

Кроме того, можно ожидать, что тип транзакции по умолчанию будет иметь значение nil, поскольку строка возврата не может быть инициализирована, верно? Что ж, похоже, что это не относится к перечислениям.

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

Контейнеры

Вы можете рассматривать контейнеры (в терминах non-swift) как место, где компилятор Swift может развернуть объекты внешнего представления (например, JSON) из коробки (т.е. контейнера), чтобы получить эквивалентный быстрый тип (например, массивы, Int, String).

Этот процесс (т. Е. Доступ к контейнеру) называется декодированием. Кодирование рассматривается как обратный процесс.

Не углубляясь в контейнеры, вы можете прочитать красивую статью Федерико Занетелло о том, как работают контейнеры здесь .

Нам нужно реализовать наш собственный init(from decoder: Decoder), чтобы взять на себя процесс декодирования по умолчанию, предоставляемый компилятором. Таким образом, мы можем использовать методы, доступные декодеру, для доступа к значениям, которые мы ожидаем от контейнера. Вот как происходит магия.

Два из трех методов, которые нам понадобятся в этой задаче-сценарии, включают:

  • Контейнеры с ключами.container<Key>(keyedBy type: Key.Type) и
  • Контейнеры с одним значением .singleValueContainer()

Контейнеры с ключами

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

Таким образом, необязательный TransactionType ? , и затем к типам транзакций можно применить необязательное разворачивание / связывание.

Важно отметить, что мы можем включить TransactionType ? с регистром .none. Это связано с тем, что Optionals в Swift представляют собой перечисление двух случаев:

  • .some (TransactionType)
  • . нет

Контейнеры с одним значением

В качестве альтернативы мы могли бы указать третий случай (например, unknown ) и использовать метод контейнера с одним значением для доступа к строковому значению типа в JSON.

Мы можем по умолчанию использовать перечисление типа транзакции для случая неизвестно, когда инициализация необработанного значения с помощью декодированного строкового значения не выполняется. Это означает, что тип всегда будет иметь значение .some (TransactionType). Теперь компилятор может обрабатывать декодирование по умолчанию структуры Транзакция, и никаких ошибок не возникает.

Лично я предпочитаю этот подход, чтобы не иметь дело с опциями 😅.

Можно ли использовать эти методы для декодирования перечислений со связанными значениями?
Хм… мы увидим 🤔 но пока мы защищаем от перечисления.

Спасибо за прочтение!! 🙇🏽‍♂️

📝 Сохраните эту историю в Журнале.

👩‍💻 Просыпайтесь каждое воскресное утро и слушайте самые интересные истории из области технологий, ожидающие вас в вашем почтовом ящике. Прочтите информационный бюллетень« Примечательно в технологиях .