- PVSM.RU - https://www.pvsm.ru -

Работы с JSON в Swift

JavaScript Object Notation [1], или сокращенно JSON является самым распространенным способом связи с сервером и получения информации с него. Он чрезвычайно популярен из-за простоты в использовании и восприятии.

Рассмотрим следующий фрагмент JSON:

[
  {
    "person": {
      "name": "Dani",
      "age": "24"
    }
  },
  {
    "person": {
      "name": "ray",
      "age": "70"
    }
  }
]


В Objective-C, парсинг и десериализация JSON достаточно простая:

NSArray *json = [NSJSONSerialization JSONObjectWithData:JSONData options:kNilOptions error:nil];
NSString *age = json[0][@"person"][@"age"];
NSLog(@"Dani's age is %@", age);

В Swift, это более сложный процесс из-за опциональных (optionals) типов и типобезопасности:

var json: Array!
do {
  json = try NSJSONSerialization.JSONObjectWithData(JSONData, options: NSJSONReadingOptions()) as? Array
} catch {
  print(error)
}
 
if let item = json[0] as? [String: AnyObject] {
  if let person = item["person"] as? [String: AnyObject] {
    if let age = person["age"] as? Int {
      print("Dani's age is (age)")
    }
  }
}

В представленном выше коде, необходимо проверять каждый объект до его использования optional binding. Это защитит ваш код; но чем сложнее JSON, тем более громоздким становится код.

В Swift 2.0, был введено оператор guard для того, чтобы избавиться от вложенных выражений if:

guard let item = json[0] as? [String: AnyObject],
  let person = item["person"] as? [String: AnyObject],
  let age = person["age"] as? Int else {
    return;
}
print("Dani's age is (age)")

Все еще многословно? Но как это упростить?

В этой статье по работе с JSON используется простейший способ парсинга JSON – используя популярную общедоступную библиотеку Gloss [2].

Так, в частности вы будете использовать Gloss для парсинга и преобразования JSON, который вмещает в себе 25 популярных приложений в US App Store. Так же просто, как и в Objective-C!

С чего начать

Скачайте стартовый playground [3] для это статьй.

Поскольку пользовательский интерфейс не нужен, будем работать исключительно с playground'oм.

Откройте Swift.playground в Xcode и изучите его.

Заметка: Вы можете заметить, что Project Navigator по умолчанию закрыт. Если это так, нажмите Command+1 чтобы вывести его на экран. У вас должно получится так как на изображению ниже.

Работы с JSON в Swift - 1

Стартовый файл playground вмещает несколько исходных и ресурсных файлов, которые полностью сосредоточены на парсинге JSON. Обратите внимание на структуру playground'a:

  • Папка Resources содержит ресурсы, доступ к которым может быть получен через ваш Swift-код
  • topapps.json: Содержит строку для парсинга JSON.
  • Папка Sources содержит дополнительные исходные файлы, к которыми ваш код в playground имеет доступ. Добавление файлов поддержки .swift в эту папку должен быть с обдуман, это принесет простоту чтения вашего playground'a.
  • App.swift: Структура старого простого Swift представляет собой приложение. Ваша цель — разбор JSON на коллекции объектов.
  • DataManager.swift: Управляет извлечением данных из локальной сети или Интернета. Используйте методы этого файла для последующей загрузки JSON.

Как только вы разобрались в данном playground'e, продолжайте читать статью!

Первичный способ разбора JSON в Swift

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

Заметка: Если вы уже изучили недостатки нативного способа парсинга JSON и хотите перейти к Gloss, пропустите следующий абзац.

Чтобы дать название приложению #1 в App Store, разберем предоставленный JSON файл.

Перед тем как начать работу со словарями, задайте дополнительное имя (alias) вверху playground'a:

typealias Payload = [String: AnyObject]

Добавьте код обратного вызова getTopAppsDataFromFileWithSuccess как показано ниже:

DataManager.getTopAppsDataFromFileWithSuccess { (data) -> Void in
 
  var json: Payload!
 
  // 1
  do {
    json = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions()) as? Payload
  } catch {
    print(error)
    XCPlaygroundPage.currentPage.finishExecution()
  }
 
  // 2
  guard let feed = json["feed"] as? Payload,
    let apps = feed["entry"] as? [AnyObject],
    let app = apps.first as? Payload
    else { XCPlaygroundPage.currentPage.finishExecution() }
 
  guard let container = app["im:name"] as? Payload,
    let name = container["label"] as? String
    else { XCPlaygroundPage.currentPage.finishExecution() }
 
  guard let id = app["id"] as? Payload,
    let link = id["label"] as? String
    else { XCPlaygroundPage.currentPage.finishExecution() }
 
  // 3
  let entry = App(name: name, link: link)
  print(entry)
 
  XCPlaygroundPage.currentPage.finishExecution()
}

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

  1. Сначала вы десериализируете данные используя NSJSONSerialization.
  2. Необходимо проверить каждое значение индексов в объекте JSON, чтобы предупредить появление nil. Как только найдено допустимое значение, продолжайте искать следующие объекты. Пройдя все индексы, вы получите значения name и link с которыми предстоит работать. Учтите, что если хотя бы один элемент JSON окажется непредвиденным, имя приложение не будет выведено на экран. Это желательная обработать в этом случаи.
  3. Последний шаг – инициализация объекта App используя значения name и link и вывод их в консоль.

Сохраните и запустите playground; вы увидите следующее в консоли отладчика:

App(name: "Game of War - Fire Age", link: "https://itunes.apple.com/us/app/game-of-war-fire-age/id667728512?mt=8&uo=2")

Да — “Game of War – Fire Age” это приложение #1 в файле JSON.

Потребовалось довольно большое количество код для получения названия первого приложения – пора посмотреть, как с этим справится Gloss.

Знакомство с преобразованием объектов с JSON

Преобразование объектов (Object mapping) представляет собой технику превращения объектов c JSON в объекты Swift. После определения моделей (model objects) и правил отображения (mapping rules), Gloss проделывает сложную работу исполняя вместо вас парсинг.

Этот способ значительно проще, чем тот, который вы использовали раньше:

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

Звучит неплохо, да? Посмотрим, как это работает!

Разбор JSON с помощью Gloss

Чтобы все выглядело безупречно, создайте новый playground Gloss.playground, затем скопируйте файл topapps.json в папку Resources и DataManager.swift и папку Sources.

Внедрение Gloss в проект

Довольно просто внедрить Gloss в ваш проект:

  1. Нажмите эту ссылку Gloss Repo Zip File [4] и сохраните библиотеку в подходящее место.
  2. Распакуйте ее и перетащите папку Gloss-master/Gloss/Gloss в папку Sources вашего playground.

Ваш Project Navigator должен выглядеть следующим образом:
Project Navigator

Это всё! Теперь Gloss добавлен в ваш проект и вы можете начать разбор JSON без головной боли!

Заметка: Gloss можно установить так же через Cocoapods. Поскольку playground их пока не поддерживают, этот способ можно использовать только при работе с проектами.

Преображение JSON в объекты

Сначала выясните как ваш объект относится к вашему JSON документу.

Необходимо, чтобы объект соответствовала протоколу Decodeable, который может декодировать их с JSON. Чтобы сделать это, осуществите инициализацию init?(json: JSON) как сказано в протоколе.

Обратите внимание на структуру topapps.json и создайте модель данных.

TopApps

Модель TopApps представляет собой объект высшего уровня, который вмещает в себе одну пару ключ-значения (key-value)

{
  "feed": {
    ...
  }
}

Создайте новый файл с названием TopApps.swift и поместите его в папке Sources вашего playground'a; добавьте следующий код:

public struct TopApps: Decodable {
 
  // 1
  public let feed: Feed?
 
  // 2
  public init?(json: JSON) {
    feed = "feed" <~~ json
  }
}
  1. Определите параметры для модели. В этом случае, он будет один. Позже вы добавите объект Feed.
  2. Реализуя пользовательский инициализатор, убедитесь, что TopApps соответствует протоколу. Вы должно быть удивитесь, что такое <~~ ! Это Encode Operator, который определяется в файле Gloss'sOperators.swift. В принципе, это говорит о том, что Gloss перемещает значения, что принадлежат ключу feed и зашифровывает (encode) их. Feed это так же объект Decodable; поэтому Gloss передаст ответственность за шифрование.

Feed
Объект Feed очень похож на объект высшего уровня. У него есть две пары ключ-значения, но поскольку нам интересны только 25 популярных приложений, нет необходимости обрабатывать объект author.

{
  "author": {
    ...
  },
  "entry": [
    ...
  ]	
}

Создайте новый файл с названием Feed.swift в папке Sources вашего playground'a и опишите его следующим образом:

public struct Feed: Decodable {
 
  public let entries: [App]?
 
  public init?(json: JSON) {
    entries = "entry" <~~ json
  }
}

App
Самым последним объектом, который надо описать является объект App. Приложение представлено в виде такой схемы:

{
  "im:name": {
    "label": "Game of War - Fire Age"
  },
  "id": {
    "label": "https://itunes.apple.com/us/app/game-of-war-fire-age/id667728512?mt=8&uo=2",	
    ...
  },
  ...
}

Создайте новый файл с названием App.swift в папке Sources вашего playground'a и добавьте следующий код:

public struct App: Decodable {
 
  // 1
  public let name: String
  public let link: String
 
  public init?(json: JSON) {
    // 2
    guard let container: JSON = "im:name" <~~ json,
      let id: JSON = "id" <~~ json
      else { return nil }
 
    guard let name: String = "label" <~~ container,
      link: String = "label" <~~ id
      else { return nil }
 
    self.name = name
    self.link = link
  }
}
  1. И Feed и TopApp использовали опциональные свойства (optional properties). Свойство может быть определено как не опциональное (non-optional) только в том случае, если использованный JSON всегда будет содержать значения для заполнения их.
  2. Не обязательно создавать объект для каждой составляющей в JSON. Например, в этом случае нет смысла создавать модель для in:name и id. Во время работы с неопциональными и вложенными объектами, не забывайте проверять nil.

Теперь, когда ваши классы готовы, остается позволить Gloss делать свою работу!
Откройте файл playground'a и добавьте следующий код:

import UIKit
import XCPlayground
 
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
 
DataManager.getTopAppsDataFromFileWithSuccess { (data) -> Void in
  var json: [String: AnyObject]!
 
  // 1
  do {
    json = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions()) as? [String: AnyObject]
  } catch {
    print(error)
    XCPlaygroundPage.currentPage.finishExecution()
  }
 
  // 2
  guard let topApps = TopApps(json: json) else {
    print("Error initializing object")
    XCPlaygroundPage.currentPage.finishExecution()
  }
 
  // 3
  guard let firstItem = topApps.feed?.entries?.first else {
    print("No such item")
    XCPlaygroundPage.currentPage.finishExecution()
  }
 
  // 4
  print(firstItem)
 
  XCPlaygroundPage.currentPage.finishExecution()
}
  1. Сначала десериализируйте данные используя NSJSONSerialization. Мы уже делали это раньше.
  2. Инициализируйте экземпляр объекта TopApps с помощью данных с JSON через конструктор.
  3. С помощью первой входной подачи получите приложение #1
  4. Выводите объект app в консоль.

Серьезно — это — весь код, в котором мы нуждаетесь.

Сохраните и запустите ваш playground; вы снова успешно получили название приложения, но более изящным способом.

App(name: "Game of War - Fire Age", link: "https://itunes.apple.com/us/app/game-of-war-fire-age/id667728512?mt=8&uo=2")

Все это относится к разбору локальных данных. Но как на счет разбора данных с удаленного ресурса?

Получение удаленного JSON

Настало время сделать этот проект более реальным. В обычном порядке, вы получаете данные c удаленного ресурса, а не с локальных файлов. Можно легко получить рейтинги с App Store с помощью сетевого запроса.

Откройте DataManager.swift и найдете URL для получения лучших приложений:

let TopAppURL = "https://itunes.apple.com/us/rss/topgrossingipadapplications/limit=25/json"

Затем добавьте следующий метод к реализации DataManager:

public class func getTopAppsDataFromItunesWithSuccess(success: ((iTunesData: NSData!) -> Void)) {
  //1
  loadDataFromURL(NSURL(string: TopAppURL)!, completion:{(data, error) -> Void in
      //2
      if let data = data {
        //3
        success(iTunesData: data)
      }
  })
}

Код выше выглядит довольно знакомым; но вместо получения данных с локального файла, мы использовали NSURLSession, чтобы получить данные с iTunes. Вот что конкретно происходит:

  1. Сначала вы вызываете метод loadDataFromURL; он требует URL-адрес и функцию замыкания, которая передает объект NSData.
  2. Используя дополнительную привязку, удостоверяемся в существовании данных.
  3. В конечном счете, вы предаете данные успешному завершению, как делали раньше.

Откройте ваш playground и замените следующий код:

DataManager.getTopAppsDataFromFileWithSuccess { (data) -> Void in

на этот

DataManager.getTopAppsDataFromItunesWithSuccess { (data) -> Void in

Теперь вы получили настоящие данные с iTunes.

Сохраните и запустите ваш playground; вы увидите, что разбор информаций по-прежнему приводит к тому же конечному итогу:

App(name: "Game of War - Fire Age", link: "https://itunes.apple.com/us/app/game-of-war-fire-age/id667728512?mt=8&uo=2")

Значение выше может отличаться, поскольку популярные приложения App Store постоянно меняются.

Часто люди не заинтересованы лишь в ТОП приложениях App Store — они хотят видеть список всех ТОП приложений. Не нужно кодить, чтобы их получить. Достаточно добавить следующий фрагмент кода:

topApps.feed?.entries

Gloss – скелеты в шкафу

Не сложно заметить, что Gloss чудесно выполняет работу парсинга – но что за этим стоит? <~~ это пользовательский оператор (custom operator) для ряда функций Decoder.decode. Gloss имеет встроенную поддержку для декодирования многих типов:

  • Простые типы (Decoder.decode)
  • Модели Decodable(Decoder.decodeDecodable)
  • Простые массивы (Decoder.decode)
  • Массивы и модели Decodable (Decoder.decodeDecodableArray)
  • Enum (Decoder.decodeEnum)
  • Массивы Enum (Decoder.decodeEnumArray)
  • NSURL (Decoder.decodeURL)
  • Массивы NSURL (Decode.decodeURLArray)

Заметка: Если у вас есть желание больше изучить Custom Operators, посмотрите статью: Operator Overloading in Swift [5]Tutorial

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

Конечно, с помощью Gloss можно так же конвертировать объекты в JSON. Если вам это интересно, просмотрите Encodable протокол.

Что дальше?

Вот и финальный playground [6].

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

Разработка Gloss продолжается тут [2] на Github, так что следите за последними обновлениями.

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

Надеюсь вам понравился этот статья о работе с JSON. Если у вас есть вопросы или замечания, их можно обсудить ниже!

Автор: yarmolchuk

Источник [7]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/json/110860

Ссылки в тексте:

[1] JavaScript Object Notation: http://www.json.org/

[2] Gloss: https://github.com/hkellaway/Gloss

[3] Скачайте стартовый playground: http://cdn5.raywenderlich.com/wp-content/uploads/2015/11/TopApps-Starter.zip

[4] Gloss Repo Zip File: https://github.com/hkellaway/Gloss/archive/master.zip

[5] Operator Overloading in Swift : http://www.raywenderlich.com/80818/operator-overloading-in-swift-tutorial

[6] финальный playground: http://cdn2.raywenderlich.com/wp-content/uploads/2015/12/Gloss-final.zip

[7] Источник: https://habrahabr.ru/post/276247/