Работаем с форматом JSON в Swift 2.1 Xcode 7

24 ноября 2015

JSON (JavaScript object notation) - это распространенный формат передачи данных на веб-серверы.

JSON очень прост в использовании и удобен для чтения, что делает его невероятно популярным.

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

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

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

NSString *age = json[0][@"person"][@"age"];
NSLog(@"Dani's age is %@", age);

Парсинг и десериализация в Swift проходят более утомительно из-за опционалов Swift:

if let item = json[0] {
  if let person = item["person"] {
    if let age = person["age"] {
      print(age)
    }
  }
}

В коде выше, каждый объект JSON должен быть проверен перед использованием, через опциональную привязку. Это делает наш код более безопасным, но и более уродливым.

В этом руководстве вы изучите более простой способ парсинга JSON в Swift, путем использования открытой библиотеки SwiftyJSON.

В частности, вы будете использовать SwiftyJSON для разбора JSON документа, который содержит 25 топовых приложений в US AppStore. Вы увидите, что разбор JSON в Swift, может быть таким же простым, как и в Objective-C.

Заметка

Чтение этого туториала предполагает наличие у вас начальных знаний в Swift. Если вы таких не имеете, то скорее проходите наше первое знакомство со Swift.

Поехали!

Скачайте начальный проект и запустите его. Вы увидите пустой view controller:

Стоит оговориться, что пользовательский интерфейс не особо важен для данного туториала, так как вы будете работать только с консолью. Привыкайте в вашему белому экрану! :]

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

  1. TopApps.json: содержит JSON для парсинга.
  2. AppModel: Файл отображающий наше приложение. Ваша цель - разобрать JSON в коллекцию этих объектов.
  3. DataManager: Управляет получением данных локально или по сети. Вы будете использовать методы этого файла для загрузки JSON.
  4. ViewController: Пустой view controller. Вы добавите сюда код для запроса данных из DataManager.swift и отпарсите его несколькими способами.
  5. Надеюсь, вы разобрались, что находится в стартовом проекте. Теперь, давайте двигаться дальше!

    Разбираем JSON нативным способом Swift

    Заметка

    Думаю, вы уже знакомы с болью, которая возникает каждый раз, когда вам нужно отпарсить JSON. И, если вы не мазохист :], то вам лучше пропустить эту секцию и сразу перейти к SwiftyJSON.

    Сначала, мы отпарсим JSON оригинальным способом Swift, то есть мы не будем использовать никаких внешних библиотек. Это поможет нам оценить пользу библиотеки SwiftyJSON.

    Давайте отпарсим предоставленный нам файл JSON, для того, чтобы получить имя первого в списке лучших приложений US AppStore.

    Откройте ViewController.swift и добавьте следующий код в конец метода viewDidLoad():

     DataManager.getTopAppsDataFromFileWithSuccess { (data) -> Void in
                // Get the number 1 app using optional binding and NSJSONSerialization
                //1
                do {
                    
                    let parsedObject: AnyObject? = try NSJSONSerialization.JSONObjectWithData(data,
                        options: NSJSONReadingOptions.AllowFragments)
                    
                    //2
                    if let topApps = parsedObject as? NSDictionary {
                        if let feed = topApps["feed"] as? NSDictionary {
                            if let apps = feed["entry"] as? NSArray {
                                if let firstApp = apps[0] as? NSDictionary {
                                    if let imname = firstApp["im:name"] as? NSDictionary {
                                        if let appName = imname["label"] as? NSString {
                                            //3
                                            print("Optional Binding: \(appName)")
                                        }
                                    }
                                }
                            }
                        }
                    }
                    
                } catch let error as NSError? {
                    print("error: \(error?.localizedDescription)")
                }
            }

    Прокручиваете глазами этот каскадный список закрывающихся скобок? :] Вот что у нас тут происходит:

    1. Сначала вы десериализуете (преобразуете из последовательного формата в параллельный), используя NSJSONSerialization.
    2. Затем, вам нужно проверить каждое значение сабскрипта в объекте JSON, чтобы убедиться, что они не nil.
    3. После того, как вы уверены, что они не nil, вы ищите следующий объект.
    4. После того, как вы прошли весь путь через все сабскрипты, вы получаете отображение значения appName.

    Если какой-либо элемент в JSON будет неожиданным, то вы получите полный беспорядок и имя приложения никогда не появится у вас на экране.

    1. Последний шаг - просто выводим значения appName в дебаггере.

    Запустите ваше приложение: вы должны увидеть в вашей консоли дебаггера вот что:

    Optional Binding: Clash of Clans

    Да, "Clash of Clans" является приложением #1 в файле JSON.

    Мы написали достаточно много кода, для того, чтобы получить всего лишь название приложения. Самое время посмотреть как будет работать SwiftyJSON.

    Интрегрирование SwiftyJSON в ваш проект

    Эту библиотеку очень просто интегрировать.

    Перейдите на страницу SwiftyJSON github. Затем, перетащите SwiftyJSON\SwiftyJSON.swift в наигацию вашего проекта Xcode. Убедитесь, что у вас стоит галочка на Copy items if needed и что у вас выбрано TopApps, затем, нажмите Finish

    Вот и все! Вы только что интегрировали библиотеку в ваш проект, и теперь вы можете парсить JSON без головной боли опциональной привязки!

    Парсим JSON через SwiftyJSON

    SwiftyJSON позволяет вам пропустить все ваши проверки значения на nil, то есть, теперь вы можете получить имя приложения #1, используя всего одно выражение if.

    Замените метод viewDidLoad() следующим:

    override func viewDidLoad() {
      super.viewDidLoad()
     
      DataManager.getTopAppsDataFromFileWithSuccess { (data) -> Void in
        // Get #1 app name using SwiftyJSON
        let json = JSON(data: data)
        if let appName = json["feed"]["entry"][0]["im:name"]["label"].string {
          print("SwiftyJSON: \(appName)")
        }
      }
    }

    И это весь код, который вам понадобится!

    Сначала, вы создаете константу JSON, через метод int типаJSON() и ваш объект данных. Затем, SwiftyJSON конвертирует эти данные в объект JSON, используя NSJSONSerialization.

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

    Итак, мы хотим получить строковое значение, по этому передаем .stringValue в конец процесса парсинга. Если необходимо получить массив, то передаем .arrayValue. Остальные возвращаемые типы используют аналогичную конструкцию.

    Запустите ваше приложение, и вы увидите, как получено имя приложения, только, теперь вы использовали значительно меньше кода:

    SwiftyJSON: Clash of Clans

    Библиотека берет на себя парсинг локальных данных, но как она работает с удаленным источником данных?

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

    Самое время сделать наш проект более реальным. Обычно, мы будем получать удаленные данные и не с локального файла. Вы можете просто взять рейтинг приложений AppStore, используя онлайн запрос.

    Идем в DataManager.swift и добавляем следующий метод:

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

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

    Сначала вы вызываете метод loadDataFromURL(), который принимает URL и замыкание (closure), которое передает объект NSData. Следующим шагом вы проверяете существование значения, используя опциональную привязку. Наконец, вы передаете данные в замыкание success, как мы делали ранее. Откройте ViewController.swift и добавьте следующие строки в конец метода viewDidLoad():

    // Get the #1 app name from iTunes and SwiftyJSON
    DataManager.getTopAppsDataFromItunesWithSuccess { (iTunesData) -> Void in
      let json = JSON(data: iTunesData)
      if let appName = json["feed"]["entry"][0]["im:name"]["label"].string {
        print("NSURLSession: \(appName)")
      }
      // More soon...
    }

    Код выше практически такой же, как тот, который вы писали в первой секции, но теперь вы получаете данные из iTunes.

    Запустите ваше приложение и вы увидите, что мы все еще получаем то же заключение, по поводу имени приложения #1:

    SwiftyJSON: Clash of Clans
    NSURLSession: Clash of Clans

    На самом деле, значения сверху могут отличаться, так как лучшие приложения AppStore тоже меняются.

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

    Разбираем JSON для заполнения массивов

    Добавьте следующий код в ViewController.swift, сразу после комментария "More soon":

    //1
    if let appArray = json["feed"]["entry"].array {
      //2
      var apps = [AppModel]()
     
      //3
      for appDict in appArray {
        let appName: String? = appDict["im:name"]["label"].string
        let appURL: String? = appDict["im:image"][0]["label"].string
     
        let app = AppModel(name: appName, appStoreURL: appURL)
        apps.append(app)
      }
     
      //4
      print(apps)
    }

    Код, который приведен выше, перебирает все приложения в ответе JSON и создает модели объекта AppModel:

    1. Сначала вы получаете список приложений с SwiftyJSON
    2. Следующим шагом вы создаете изменяемый массив для хранения объектов, которые будут созданы.
    3. Затем, вы перебираете все элементы и создаете AppModel из данных JSON.
    4. И последнее, вы выводите на экран список новых объектов в консоли дебаггера.

    Запустите приложение. Вы увидите предыдущие три результата попытки разбора JSON, а затем, список 25 лучших приложений в iTunes store:

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

    Вот и все что нужно, для использования JSON в Swift. Остальная часть туториала опциональна, так как она посвящена работе SwiftyJSON непосредственно под "капотом".

    Заглядываем под капот SwiftyJSON

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

    Когда вы вызываете JSON(), вы передаете экземпляр опционального NSData или даже AnyObject. JSONValue() является инициализатором для перечисления (enum) в SwiftyJSON:

    public enum JSON {
        case ScalarNumber(NSNumber)
        case ScalarString(String)
        case Sequence(Array)
        case Mapping(Dictionary)
        case Null(NSError?)
        // ...
    }

    Помните, что для того чтобы получить разные значения из возвращенного JSON, вам нужны разные ключи и индексы. Ниже приведен код, который позволяет вам получить имя приложения #1 в AppStore:

    json["feed"]["entry"][0]["im:name"]["label"]

    Оно работает через сабскрипт языка Swift:

    extension JSON {    
        // ...    
        subscript(key: String) -> JSON {
            get {
                switch self {
                case .Mapping(let dictionary) where dictionary[key] != nil:
                    return dictionary[key]!
                default:
                    return .Null(NSError(domain: SwiftyJSONErrorDomain, code: 0, userInfo: nil))
                }
            }
        }
    }

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

    extension JSON {
     
        var string: String? {
            get {
                switch self {
                case .ScalarString(let string):
                    return string
                case .ScalarNumber(let number):
                    return number.string
                default:
                    return nil
                }
            }
        }
     
        // ...
    }

    Метод string() один из множества способов, которые названы в честь типа, который они возвращают из объекта JSON. Каждая функция возвращает опциональную тезку типа.

    var string: String?
    var number: NSNumber?
    var URL: NSURL?
    var char: Int8?
    var unsignedChar: UInt8?
    var short: Int16?
    var unsignedShort: UInt16?
    var long: Int?
    var unsignedLong: UInt?
    var longLong: Int64?
    var unsignedLongLong: UInt64?
    var float: Float?
    var double: Double?
    var integer: Int?
    var unsignedInteger: Int?

    Обратите внимание, что все значения являются опциональными, так что мы защищены от значения равного nil. Вот так SwiftyJSON обрабатывает опциональную привязку.

    Но, мы пока что еще не затронули обработку ошибки в SwiftyJSON. Вот как можно обработать неправильное условие в SwiftyJSON:

    let json = JSON(dataFromNetworking)["some_key"]["some_wrong_key"]["wrong_name"]
    if json{
      // обработка удачна
    } else {
      print(json)
      //> JSON Keypath Error: Incorrect Keypath "some_wrong_key/wrong_name"
      //вам всегда сообщается, где ваш ключ стал некорректным
      switch json{
      case .JInvalid(let error):
        // NSError содержит детальный отчет об ошибке 
      }
    }

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

    Конечный проект вы можете скачать здесь.

    Что дальше?

    Дальше, вы можете продолжить изучать наши туториалы по мере их появления, а также, параллельно читать перевод официальной книги по языку программирования Swift. И, для более подробного изучения языка, вы можете пройти наши курсы!

    Урок подготовил: Иван Акулов

    Источник урока: http://www.raywenderlich.com/82706/working-with-json-in-swift-tutorial

Содержание