Tehnografi.com - Технологические новости, обзоры и советы

Работа с Codable и JSON в Swift

Следующая статья поможет вам: Работа с Codable и JSON в Swift

Вернуться в блог

Вы можете использовать Codable в Swift для кодирования и декодирования пользовательских форматов данных, таких как JSON, в собственные объекты Swift. Невероятно легко сопоставить объекты Swift с данными JSON и наоборот, просто приняв протокол Codable.

Как прагматичный разработчик iOS, вы рано или поздно столкнетесь с JSON. Каждый веб-сервис, от Facebook в Foursquare, использует формат JSON для передачи данных в ваше приложение и из него. Как можно эффективно кодировать и декодировать данные JSON в объекты Swift?

В этом руководстве по разработке приложений вы узнаете, как работать с объектами JSON в Swift с помощью протокола Codable. То, что вы узнаете, можно легко применить и к другим форматам данных. Мы займемся кодированием и декодированием с помощью JSONEncoder и JSONDecoder, и я покажу вам, как быть посредником между данными JSON и Swift.

Какую проблему на самом деле решает протокол Codable в Swift? Начнем с примера.

Представьте, что вы создаете приложение для рецептов. Приложение показывает список различных рецептов, включая ингредиенты, инструкции и основную информацию о еде.

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

Вот пример данных JSON для рецепта:

{
«имя»: «Спагетти Болоньезе»,
«автор»: «Кулинарный уголок Рейндера»,
«url»: «https://cookingcorner.com/spaghetti-bolognese»,
«доходность»: 4,
«ингредиенты»: [“cheese”, “love”, “spaghetti”, “beef”, “tomatoes”, “garlic”],
«инструкция»: «Сварите спагетти, обжарьте говядину с чесноком, добавьте помидоры, добавьте любовь, ешьте!»
}

Взгляните на структуру данных JSON.

Объекты JSON заключаются в волнистые скобки { и }, а массивы заключаются в квадратные скобки. [ and ]. Имена свойств представляют собой строки, заключенные в кавычки «. Значениями в JSON могут быть строки, числа (без кавычек), массивы или другие объекты. Вы также можете вкладывать данные, т. е. массивы в массивы, объекты в массивы и т. д., чтобы создать сложную иерархию данных.

JSON — это текстовый формат данных, который используют многие веб-сервисы, включая API от Twitter, Facebook, Foursquare и так далее. Если вы создаете приложения, использующие веб-ресурсы, вы столкнетесь с JSON.

Формат JSON превосходит распространенную альтернативу XML, поскольку он эффективен, легко анализируется и читается людьми. JSON — это согласованный формат для веб-сервисов, API и приложений. Он используется в Интернете, приложениях и онлайн-сервисах, поскольку формат прост и гибок.

И у JSON есть одна превосходная возможность: вы можете кодировать любой формат данных в JSON и декодировать JSON обратно в любой формат данных. Этот процесс кодирования и декодирования делает JSON таким мощным.

Вы можете взять значения Swift String, Int, Double, URL, Date, Data, Array и Dictionary и закодировать их как JSON. Затем вы отправляете их веб-сервису, который декодирует значения в понятный ему собственный формат. Аналогично, веб-сервис отправляет в ваше приложение данные, закодированные как JSON, и вы декодируете их в собственные типы, такие как String, Double и Array.

Когда ваше приложение-рецепт получает JSON (см. выше), его можно декодировать в структуру Swift, например:

структура Рецепт {
имя переменной: String
вар автор: String
вар URL: URL
выход вар: Int
вар ингредиенты: [String]
инструкции var: строка
}

В Swift протокол Codable используется для перехода от объекта данных JSON к реальному классу или структуре Swift. Это называется декодированием, поскольку данные JSON декодируются в формат, понятный Swift. Это также работает и по-другому: кодирование объектов Swift как JSON.

Протокол Codable в Swift на самом деле является псевдонимом протоколов Decodeable и Encodable. Поскольку вы часто используете кодирование и декодирование вместе, вы используете протокол Codable, чтобы получить оба протокола за один раз.

Центральным элементом рабочего процесса кодирования/декодирования является протокол Swift Codable. Давайте узнаем, как это работает, дальше!

Не можете отличить «кодирование» от «декодирования»? Подумайте об этом так: мы преобразуем данные из «кода» и в «код», подобно машине «Энигма» или секретному криптошифру. Кодирование означает преобразование данных в код; кодирование или «внутри кода». Декодирование означает преобразование кода в данные; декодирование, или «код включения/выключения».

Использование JSON до Swift 4 было чем-то вроде PITA. Вам придется самостоятельно сериализовать JSON с помощью JSONSerialization, а затем ввести приведение каждого свойства JSON к нужному типу Swift.

Так:

пусть json = попробует? JSONSerialization.jsonObject(с: данными, опциями: [])

если let рецепт = json как? [String: Any] {
если let выход = рецепт[“yield”] как? Int {
рецептОбъект.доход = выход
}
}

Приведенный выше фрагмент только получает значение доходности из JSON и преобразует его в Int. Он чрезвычайно многословен, и сложно правильно отреагировать на потенциальные ошибки и несоответствия типов. Хоть это и работает, но не идеально.

Такие библиотеки, как SwiftyJSON, значительно упрощают работу с JSON, но вам все равно необходимо сопоставить данные JSON с соответствующими объектами и свойствами Swift.

В Swift 4 и более поздних версиях вместо этого вы можете использовать протокол Codable. Ваша структура или класс Swift просто должен принять протокол Codable, и вы получите кодирование и декодирование JSON «из коробки» бесплатно.

Кодируемый протокол представляет собой комбинацию двух протоколов: декодируемого и кодируемого. Оба протокола довольно минимальны; они определяют только функции init(from: Decoder) и encode(to: Encoder) соответственно. Другими словами, эти функции означают, что для того, чтобы тип был «декодируемым» или «кодируемым», ему необходимо «декодировать из чего-то» и «кодировать во что-то».

Настоящее волшебство Codable происходит с протоколами Decoder и Encoder. Эти протоколы используются компонентами, которые кодируют/декодируют различные форматы данных, например JSON. В Swift у нас есть несколько -кодеров:

  • PropertyListEncoder и PropertyListDecoder для списков свойств .plist
  • JSONEncoder и JSONDecoder для JSON — это мы!
  • NSKeyedArchiver может работать с Codable через PropertyListEncoder, Data и NSCoding.

Классы JSONDecoder и JSONEncoder используют протоколы Decoder и Encoder для предоставления функций декодирования/кодирования JSON. Это также означает, что вы можете написать свой собственный кодер/декодер для Codable при условии, что вы используете протоколы декодера и кодировщика!

Хотите узнать больше о протоколах в Swift? Прочтите «Протоколы в Swift Объяснено», чтобы наверстать упущенное.

Давайте посмотрим на пример. Мы собираемся сопоставить некоторые данные JSON со структурой Swift. По сути, мы декодируем JSON в реальный объект Swift.

Сначала мы создаем структуру под названием User. Так:

Пользователь структуры: Кодируемый {
вар first_name: Строка
вар Last_name: Строка
переменная страна: String
}

Структура User имеет три простых свойства типа String и соответствует протоколу Codable.

Затем давайте напишем немного JSON. Это JSON, с которым мы будем работать:

{
«first_name»: «Джон»,
«last_name»: «Доу»,
«страна»: «Великобритания»
}

Данные JSON обычно поступают в ваше приложение в ответ на запрос веб-службы или через файл .json, но в этом примере мы просто помещаем JSON в строку. Так:

пусть jsonString = «»»
{
«first_name»: «Джон»,
«last_name»: «Доу»,
«страна»: «Великобритания»
}
«»»

Note: В приведенном выше коде тройная кавычка «»» используется для создания многострочных строк. Аккуратный!

Далее мы декодируем JSON и превращаем его в объект User. Так:

пусть jsonData = jsonString.data(используя: .utf8)!
пусть пользователь = попробуй! JSONDecoder().decode(User.self, from: jsonData)
печать(пользователь.фамилия_имя)
// Вывод: Доу

Вот что происходит:

  • Сначала вы превращаете jsonString в объект Data, вызывая функцию data(using:) для строки. Это необходимый промежуточный шаг.
  • Затем вы создаете объект JSONDecoder и немедленно вызываете для него функцию decode(_:from:). Это превращает jsonData в объект типа User путем декодирования JSON. Тип User.self предоставляется в качестве параметра.
  • Наконец, вы распечатываете фамилию пользователя с помощью print(user.last_name). Типом этого пользовательского значения является User, так что это настоящий объект Swift!

Легко, правда? По сути, вы «сопоставили» объект JSON со структурой Swift и декодировали формат JSON в собственный объект, с которым Swift может работать.

В приведенном выше коде мы игнорируем любые ошибки, возникающие при вызове decode(_:from:) с помощью try!. В вашем собственном коде вам нужно будет обрабатывать ошибки с помощью блока do-try-catch. Например, ошибка, которая может возникнуть, связана с предоставлением недопустимого JSON.

User.self — это метатип, ссылка на сам тип User. Мы сообщаем декодеру, что хотим декодировать данные с помощью структуры User, предоставляя ей ссылку на этот тип.

Распространенной ошибкой при определении типа Codable, например структуры User, является несовпадение ключей и свойств. Если вы добавили в структуру свойство, которого нет в JSON или оно имеет другой тип, при декодировании JSON вы получите сообщение об ошибке. Обратное, то есть свойство, которого нет в вашей структуре, но есть в JSON, не является проблемой. Проще всего отлаживать это свойство по одному, т. е. продолжать добавлять свойства до тех пор, пока JSON больше не перестанет декодироваться без ошибок. Вы также можете определить, какие свойства/ключи включать или игнорировать с помощью перечисления CodingKeys (см. ниже).

Давайте еще раз взглянем на пример из предыдущего раздела и расширим его. Вот JSON, с которым мы работаем:
пусть jsonString = «»»
{
«first_name»: «Джон»,
«last_name»: «Доу»,
«страна»: «Великобритания»
}
«»»
Затем мы превращаем это в объект данных. Так:

пусть jsonData = jsonString.data(используя: .utf8)!
Это необходимый промежуточный шаг. Вместо представления JSON в виде строки мы теперь сохраняем JSON как собственный объект данных. Кодировка символов, которую мы используем для этой строки, — UTF8.

Если вы присмотритесь, то увидите, что приведенный выше код использует принудительное развертывание для работы с необязательным возвращаемым значением из данных (с использованием:). Давайте развернём этот опцион более элегантно!

если пусть jsonData = jsonString.data (с использованием: .utf8)
{
// Используем `jsonData`
} еще {
// Реагируем на ошибку
}
С помощью приведенного выше кода мы можем реагировать на ошибки, когда data(using:) возвращает ноль. Например, вы можете показать сообщение об ошибке или молча допустить сбой задачи и сохранить диагностическую информацию в журнале.

Далее мы создаем новый объект JSONDecoder. Так:

пусть декодер = JSONDecoder()

Затем мы используем этот декодер для декодирования данных JSON. Так:

пусть пользователь = попробуй! decoder.decode(User.self, из: jsonData)

Однако функция decode(_:from:) может выдавать ошибки. Приведенный выше код аварийно завершает работу всякий раз, когда это происходит. Опять же, мы хотим реагировать на любые ошибки, которые могут произойти. Так:

делать {
let user = попробуйте decoder.decode(User.self, from: jsonData)
печать(пользователь.фамилия_имя)
} ловить {
печать (error.localizedDescription)
}
И весь блок кода теперь выглядит следующим образом. Видите, как это отличается?

если пусть jsonData = jsonString.data (с использованием: .utf8)
{
пусть декодер = JSONDecoder()

делать {
let user = попробуйте decoder.decode(User.self, from: jsonData)
печать(пользователь.фамилия_имя)
} ловить {
печать (error.localizedDescription)
}
}
Никогда не замалчивайте ошибки. Отловите ошибку и отреагируйте на нее либо с помощью пользовательского интерфейса/UX, повторив задачу, либо путем регистрации, сбоя и исправления.

Работа с CodingKeys

Что, если наши свойства JSON, такие как first_name и/или firstName, отличаются от свойств структуры Swift? Вот тут-то и приходит на помощь CodingKeys.

Каждый класс или структура, соответствующая Codable, может объявить специальное вложенное перечисление CodingKeys. Вы используете его для определения свойств, которые следует кодировать и декодировать, включая их имена.

Давайте посмотрим на пример. В приведенной ниже структуре User мы изменили имена свойств со Snake_case на CamelCase. Например, ключ first_name теперь называется firstName.

структура Пользователь:Кодируемый
{
вар firstName: String
вар LastName: String
переменная страна: String

перечисление CodingKeys: String, CodingKey {
случай firstName = «первое_имя»
случай LastName = «фамилия_имя»
страна дела
}
}

Когда вы используете приведенную выше структуру с примерами из предыдущих разделов, вы увидите, что можете использовать объект User с новыми именами свойств. Так:

печать(пользователь.первоеимя)
// Вывод: Джон

печать(пользователь.страна)
// Выход: Великобритания

Перечисление CodingKeys сопоставляет регистры со свойствами и использует строковые значения для выбора правильных имен свойств в данных JSON. Регистр в перечислении — это имя свойства, которое вы хотите использовать в структуре, а его значение — это имя ключа в данных JSON.

До сих пор мы сосредоточились на декодировании данных JSON в объекты Swift. А как насчет того, чтобы пойти другим путем? Можем ли мы также кодировать объекты в формате JSON? Да, почему бы и нет!

Так:
вар пользователь = Пользователь()
user.firstName = «Боб»
user.lastName = «и Алиса»
user.country = «Криптоленд»

пусть jsonData = попробуй! JSONEncoder().encode(пользователь)
пусть jsonString = String (данные: jsonData, кодировка: .utf8)!
печать (jsonString)

И результат:

{«country»: «Cryptoland», «first_name»: «Боб», «last_name»: «и Алиса»}

Что здесь происходит?

  • Сначала вы создаете объект «Пользователь» и назначаете некоторые значения его свойствам.
  • Затем вы используете encode(_:) для кодирования объекта пользователя в объект данных JSON.
  • Наконец, вы конвертируете объект Data в String и распечатываете его.
  • Если вы присмотритесь, вы увидите, что кодирование повторяет те же шаги, что и декодирование, за исключением обратного порядка. Переход от объекта Swift через декодер, в результате чего получается строка JSON.

    Можем ли мы, как и раньше, расширить пример для устранения ошибок? Да! Так:

    пусть кодер = JSONEncoder()
    encoder.outputFormatting = .prettyPrinted

    делать {
    let jsonData = попробуйте encoder.encode(пользователь)

    if let jsonString = String (данные: jsonData, кодировка: .utf8) {
    печать (jsonString)
    }
    } ловить {
    печать (error.localizedDescription)
    }

    В приведенном выше примере мы также используем свойство выходного формата кодировщика для «хорошей печати» данных JSON. При этом добавляются пробелы, табуляции и новые строки, чтобы облегчить чтение строки JSON. Так:

    {
    «страна»: «Криптоленд»,
    «first_name»: «Боб»,
    «last_name»: «и Алиса»
    }

    Потрясающий!

    Стоит отметить, что словари в Swift и JSON не имеют фиксированного порядка сортировки. Вот почему вы увидите разные порядки сортировки для ключей страны, имени и т. д. в JSON, если вы запустите приведенный выше код несколько раз. Большинство веб-сервисов, выводящих JSON, обеспечивают фиксированный порядок сортировки через схему, что определенно упрощает чтение и навигацию по JSON.

    До сих пор мы работали с простыми объектами JSON — просто классом с несколькими свойствами. Но что, если данные, с которыми вы работаете, более сложны?

    Например, данные JSON, которые вы можете получить обратно из Twitter или Facebook веб-сервис часто является вложенным. То есть данные, к которым вы хотите получить доступ, запрятаны на 2-3 уровнях JSON-массивов и словарей. Что теперь!?

    Давайте сначала рассмотрим простой пример. Вот структура Swift, с которой мы будем работать:

    Пользователь структуры: Кодируемый {
    вар first_name: Строка
    вар Last_name: Строка
    }

    Затем вот данные JSON, которые мы хотим декодировать:

    пусть jsonString = «»»
    [
    {
    “first_name”: “Arthur”,
    “last_name”: “Dent”
    }, {
    “first_name”: “Zaphod”,
    “last_name”: “Beeblebrox”
    }, {
    “first_name”: “Marvin”,
    “last_name”: “The Paranoid Android”
    }
    ]
    «»»
    Если присмотреться, то видно, что нужные нам данные хранятся в виде массива. JSON — это не один простой объект, а массив с тремя объектами User. Мы знаем, что это массив объектов, благодаря квадратным скобкам. [ ] (массив) и волнистые скобки { } (объект). Несколько элементов всегда разделяются запятыми.

    Как мы можем декодировать эти объекты User? Вот как:

    пусть jsonData = jsonString.data(используя: .utf8)!
    пусть пользователи = попробуют! JSONDecoder().decode([User].self, из: jsonData)

    для пользователя в пользователях {
    печать(пользователь.первое_имя)
    }

    Приведенный выше фрагмент кода очень похож на то, что вы видели раньше, но есть одно важное отличие. Видите тип, который мы предоставляем функции decode(_:from:)? Тип [User] (с расширением .self) или «массив объектов пользователя». Вместо того, чтобы работать с одним объектом User, мы хотим декодировать несколько из них, и это обозначается значком [User] тип массива.

    Далее мы обсудим, как можно работать с более сложными вложенными типами. Предположим, например, что массив объектов User вложен в словарь JSON. Так:

    пусть jsonString = «»»
    {
    «пользователи»:
    [
    {
    “first_name”: “Arthur”,
    “last_name”: “Dent”
    }, {
    “first_name”: “Zaphod”,
    “last_name”: “Beeblebrox”
    }, {
    “first_name”: “Marvin”,
    “last_name”: “The Paranoid Android”
    }
    ]
    }
    «»»
    В приведенном выше фрагменте JSON элементом верхнего уровня является словарь (или «объект»). У него есть только одна пара ключ-значение: массив пользователей. Данные, которые нам нужны, находятся внутри этого массива. Как мы доберемся до этого?

    Важно, чтобы наш подход не был слишком сложным. Легко подумать, что вам понадобится более сложный анализ JSON, но, как оказалось, мы также можем вкладывать структуры Swift. Мы собираемся описать весь JSON как структуру со структурой User внутри нее.

    Вот, проверьте это:

    Ответ структуры: Кодируемый
    {
    Пользователь структуры: Кодируемый {
    вар first_name: Строка
    вар Last_name: Строка
    }

    пользователи вар: [User]
    }

    Вот что происходит:

    • Словарь JSON верхнего уровня соответствует типу ответа. Как и раньше, этот тип соответствует Codable для поддержки кодирования и декодирования JSON.
    • Внутри этой структуры мы определили другую структуру под названием User. Это тот же самый тип, который мы использовали раньше. Эта структура является «вложенной».
    • Структура Response имеет одно свойство, называемое пользователями типа [User]или массив объектов User.

    Самое интересное то, что семантически структуры данных JSON и Swift совершенно одинаковы. Они просто используют другой синтаксис. Мы определили вложенный массив внутри словаря верхнего уровня, точно так же, как структура Response имеет вложенную структуру User и свойство пользователей внутри нее.

    Заставить его работать теперь проще простого. Проверь это:

    пусть jsonData = jsonString.data(используя: .utf8)!
    пусть ответ = попробуйте! JSONDecoder().decode(Response.self, from: jsonData)

    для пользователя в ответе.users {
    печать(пользователь.первое_имя)
    }

    Посмотрите, как мы используем тип Response для декодирования JSON, а затем перебираем свойство response.users? Проверьте это с помощью структуры Response и JSON. Мы выбираем пару «ключ-значение пользователя» в словаре верхнего уровня и сопоставляем объекты внутри с объектами «Пользователь». Аккуратный!

    Использование вложенных типов — отличный универсальный подход к сложным структурам данных JSON. Когда вы сталкиваетесь с небольшим количеством JSON, который невозможно легко представить в материальном типе, например User или Tweet, попробуйте расширить тип до чего-то вроде Response или UserCollection. Вместо того, чтобы идти глубже, идите шире и включите полный JSON. Используйте вложенные типы, чтобы получить нужные данные. Здесь также стоит отметить, что вам не обязательно добавлять структуру User в структуру Response — она также может выходить за ее пределы.

    И это все, что нужно! Теперь вы знаете:

    • Как использовать Codable для создания объектов, которые можно кодировать и декодировать
    • Как использовать JSONDecoder и JSONEncoder для кодирования и декодирования объектов JSON и их аналогов Swift.
    • Для чего нужны кодирование и декодирование и почему это важно в повседневной разработке iOS
    • Как работать с более сложными данными JSON и как использовать вложенные типы