Корректный разбор JSON в Swift 3


Я пытаюсь получить ответ JSON и сохранить результаты в переменной. У меня были версии этого кода в предыдущих версиях Swift, пока не была выпущена версия GM Xcode 8. Я посмотрел на несколько подобных сообщений на StackOverflow: Swift 2 Parsing JSON - не может индексировать значение типа "AnyObject" и JSON Parsing в Swift 3.

однако, похоже, что идеи, переданные там, не применяются в этом сценарии.

Как правильно разобрать JSON ответ в Swift 3? Что-то изменилось в способе чтения JSON в Swift 3?

Ниже приведен код в вопросе (он может быть запущен на детской площадке):

import Cocoa

let url = "https://api.forecast.io/forecast/apiKey/37.5673776,122.048951"

if let url = NSURL(string: url) {
    if let data = try? Data(contentsOf: url as URL) {
        do {
            let parsedData = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments)

        //Store response in NSDictionary for easy access
        let dict = parsedData as? NSDictionary

        let currentConditions = "(dict!["currently"]!)"

        //This produces an error, Type 'Any' has no subscript members
        let currentTemperatureF = ("(dict!["currently"]!["temperature"]!!)" as NSString).doubleValue

            //Display all current conditions from API
            print(currentConditions)

            //Output the current temperature in Fahrenheit
            print(currentTemperatureF)

        }
        //else throw an error detailing what went wrong
        catch let error as NSError {
            print("Details of JSON parsing error:n (error)")
        }
    }
}

Edit: вот пример результатов от вызова API после print(currentConditions)

["icon": partly-cloudy-night, "precipProbability": 0, "pressure": 1015.39, "humidity": 0.75, "precipIntensity": 0, "windSpeed": 6.04, "summary": Partly Cloudy, "ozone": 321.13, "temperature": 49.45, "dewPoint": 41.75, "apparentTemperature": 47, "windBearing": 332, "cloudCover": 0.28, "time": 1480846460]
6 99

6 ответов:

в первую очередь никогда не загружайте данные синхронно с удаленного URL, используйте всегда асинхронные методы, такие как URLSession.

' Any ' не имеет подписчиков

происходит потому, что компилятор понятия не имеет, какого типа промежуточные объекты (например currently на ["currently"]!["temperature"]) и поскольку вы используете типы коллекций Foundation, такие как NSDictionary компилятор понятия не имеет о типе вообще.

дополнительно в Swift 3 требуется сообщить компилятору о типе все индексного объектов.

вы должны привести результат сериализации JSON к фактическому типу.

этот код использует URLSession и исключительно Swift native types

let urlString = "https://api.forecast.io/forecast/apiKey/37.5673776,122.048951"

let url = URL(string: urlString)
URLSession.shared.dataTask(with:url!) { (data, response, error) in
  if error != nil {
    print(error)
  } else {
    do {

      let parsedData = try JSONSerialization.jsonObject(with: data!) as! [String:Any]
      let currentConditions = parsedData["currently"] as! [String:Any]

      print(currentConditions)

      let currentTemperatureF = currentConditions["temperature"] as! Double
      print(currentTemperatureF)
    } catch let error as NSError {
      print(error)
    }
  }

}.resume()

для печати всех пар ключ / значение currentConditions вы могли бы написать

 let currentConditions = parsedData["currently"] as! [String:Any]

  for (key, value) in currentConditions {
    print("\(key) - \(value) ")
  }

Примечание относительно jsonObject(with data:

многие (кажется, все) учебники предлагают .mutableContainers или .mutableLeaves параметры, которые совершенно бессмысленны в Swift. Эти два варианта являются устаревшими параметрами Objective-C, чтобы назначить результат NSMutable... объекты. В Swift какие-либо variable по умолчанию является изменяемым и передает любой из этих параметров и присваивает результат let постоянная, не имеет никакого эффекта вообще. Кроме того, большинство реализаций никогда не мутируют десериализованный JSON в любом случае.

в единственный (редкий) вариант, который полезен в Swift-это .allowFragments что требуется, если корневой объект JSON может быть типом значения (String,Number,Bool или null) вместо одного из типов коллекций (array или dictionary). Но, как правило, не указывать - JSON: числовые значения не в двойные кавычки 123 или 123.0 – Swift:Int или Double

  • Буль - JSON:true или false не в двойных кавычках-Swift:true или false
  • null - JSON:null – Swift:NSNull
  • согласно спецификации JSON все ключи в словарях должны быть String.


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

    если корневой объект является словарем ({}) привести тип к [String:Any]

    if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [String:Any] { ...
    

    и получить значения по ключам с (OneOfSupportedJSONTypes - это либо коллекция JSON, либо тип значения, как описано выше.)

    if let foo = parsedData["foo"] as? OneOfSupportedJSONTypes {
        print(foo)
    } 
    

    если корневой объект является массивом ([]) приведите тип к [[String:Any]]

    if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]] { ...
    

    и перебирать массив с

    for item in parsedData {
        print(item)
    }
    

    Если вам нужен элемент в определенном индексе, проверьте также, существует ли индекс

    if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]], parsedData.count > 2,
       let item = parsedData[2] as? OneOfSupportedJSONTypes {
          print(item)
        }
    }
    

    в том редком случае, что JSON просто один из типов значений-а не тип коллекции – вы должны передать и привести результат к соответствующему типу значения, например

    if let parsedData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? String { ...
    

    Apple опубликовала исчерпывающую статью в блоге Swift: работа с JSON в Swift

    большое изменение, которое произошло с Xcode 8 Beta 6 для Swift 3, заключалось в том, что id теперь импортируется как Any, а не AnyObject.

    это означает, что parsedData возвращается в виде словаря скорее всего с типом [Any:Any]. Без использования отладчика я не мог сказать вам точно, что ваш бросок к NSDictionary будет делать, но ошибка, которую вы видите, потому что dict!["currently"]! типа Any

    Итак, как вы решаете эту проблему? Из того, как вы ссылались на него, я предполагаю dict!["currently"]! - это словарь и так у вас есть много вариантов:

    во-первых, вы могли бы сделать что-то вроде этого:

    let currentConditionsDictionary: [String: AnyObject] = dict!["currently"]! as! [String: AnyObject]  
    

    это даст вам объект словаря, который вы можете запросить значения, и поэтому вы можете получить свою температуру следующим образом:

    let currentTemperatureF = currentConditionsDictionary["temperature"] as! Double
    

    или если вы предпочитаете, вы можете сделать это в строку:

    let currentTemperatureF = (dict!["currently"]! as! [String: AnyObject])["temperature"]! as! Double
    

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

    последнее примечание: самый простой что нужно сделать, может быть, просто бросить полезную нагрузку JSON в [String: AnyObject] в самом начале.

    let parsedData = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments) as! Dictionary<String, AnyObject>
    
    let str = "{\"names\": [\"Bob\", \"Tim\", \"Tina\"]}"
    
    let data = str.data(using: String.Encoding.utf8, allowLossyConversion: false)!
    
    do {
        let json = try JSONSerialization.jsonObject(with: data, options: []) as! [String: AnyObject]
        if let names = json["names"] as? [String] 
    {
            print(names)
    }
    } catch let error as NSError {
        print("Failed to load: \(error.localizedDescription)")
    }
    

    Обновлено isConnectToNetwork-функция впоследствии, благодаря этому сообщению проверьте подключение к интернету с помощью Swift

    Я написал дополнительный метод для этого:

    import SystemConfiguration
    
    func loadingJSON(_ link:String, postString:String, completionHandler: @escaping (_ JSONObject: AnyObject) -> ()) {
        if(isConnectedToNetwork() == false){
           completionHandler("-1" as AnyObject)
           return
        }
    
        let request = NSMutableURLRequest(url: URL(string: link)!)
        request.httpMethod = "POST"
        request.httpBody = postString.data(using: String.Encoding.utf8)
    
        let task = URLSession.shared.dataTask(with: request as URLRequest) { data, response, error in
        guard error == nil && data != nil else {                                                          // check for fundamental networking error
            print("error=\(error)")
            return
            }
    
        if let httpStatus = response as? HTTPURLResponse , httpStatus.statusCode != 200 {           // check for http errors
            print("statusCode should be 200, but is \(httpStatus.statusCode)")
            print("response = \(response)")
        }
    
    
        //JSON successfull
        do {
    
            let parseJSON = try JSONSerialization.jsonObject(with: data!, options: .allowFragments)
    
            DispatchQueue.main.async(execute: {
                completionHandler(parseJSON as AnyObject)
            });
    
    
        } catch let error as NSError {
            print("Failed to load: \(error.localizedDescription)")
    
        }
      }
      task.resume()
    }
    
    
    func isConnectedToNetwork() -> Bool {
    
        var zeroAddress = sockaddr_in(sin_len: 0, sin_family: 0, sin_port: 0, sin_addr: in_addr(s_addr: 0), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
        zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress))
        zeroAddress.sin_family = sa_family_t(AF_INET)
    
        let defaultRouteReachability = withUnsafePointer(to: &zeroAddress) {
            .withMemoryRebound(to: sockaddr.self, capacity: 1) {zeroSockAddress in
                SCNetworkReachabilityCreateWithAddress(nil, zeroSockAddress)
            }
        }
    
        var flags: SCNetworkReachabilityFlags = SCNetworkReachabilityFlags(rawValue: 0)
        if SCNetworkReachabilityGetFlags(defaultRouteReachability!, &flags) == false {
            return false
        }
    
        let isReachable = (flags.rawValue & UInt32(kSCNetworkFlagsReachable)) != 0
        let needsConnection = (flags.rawValue & UInt32(kSCNetworkFlagsConnectionRequired)) != 0
        let ret = (isReachable && !needsConnection)
    
        return ret
    
    }
    

    Так что теперь вы можете легко назвать это в вашем приложении, где вы хотите

    loadingJSON("yourDomain.com/login.php", postString:"email=\(userEmail!)&password=\(password!)") {
                parseJSON in
    
                if(String(describing: parseJSON) == "-1"){
                    print("No Internet")
                } else {
    
                    if let loginSuccessfull = parseJSON["loginSuccessfull"] as? Bool {
                        //... do stuff
                    }
      }
    

    Я построил quicktype именно для этой цели. Просто вставьте образец JSON и quicktype генерирует эту иерархию типов для ваших данных API:

    struct Forecast {
        let hourly: Hourly
        let daily: Daily
        let currently: Currently
        let flags: Flags
        let longitude: Double
        let latitude: Double
        let offset: Int
        let timezone: String
    }
    
    struct Hourly {
        let icon: String
        let data: [Currently]
        let summary: String
    }
    
    struct Daily {
        let icon: String
        let data: [Datum]
        let summary: String
    }
    
    struct Datum {
        let precipIntensityMax: Double
        let apparentTemperatureMinTime: Int
        let apparentTemperatureLowTime: Int
        let apparentTemperatureHighTime: Int
        let apparentTemperatureHigh: Double
        let apparentTemperatureLow: Double
        let apparentTemperatureMaxTime: Int
        let apparentTemperatureMax: Double
        let apparentTemperatureMin: Double
        let icon: String
        let dewPoint: Double
        let cloudCover: Double
        let humidity: Double
        let ozone: Double
        let moonPhase: Double
        let precipIntensity: Double
        let temperatureHigh: Double
        let pressure: Double
        let precipProbability: Double
        let precipIntensityMaxTime: Int
        let precipType: String?
        let sunriseTime: Int
        let summary: String
        let sunsetTime: Int
        let temperatureMax: Double
        let time: Int
        let temperatureLow: Double
        let temperatureHighTime: Int
        let temperatureLowTime: Int
        let temperatureMin: Double
        let temperatureMaxTime: Int
        let temperatureMinTime: Int
        let uvIndexTime: Int
        let windGust: Double
        let uvIndex: Int
        let windBearing: Int
        let windGustTime: Int
        let windSpeed: Double
    }
    
    struct Currently {
        let precipProbability: Double
        let humidity: Double
        let cloudCover: Double
        let apparentTemperature: Double
        let dewPoint: Double
        let ozone: Double
        let icon: String
        let precipIntensity: Double
        let temperature: Double
        let pressure: Double
        let precipType: String?
        let summary: String
        let uvIndex: Int
        let windGust: Double
        let time: Int
        let windBearing: Int
        let windSpeed: Double
    }
    
    struct Flags {
        let sources: [String]
        let isdStations: [String]
        let units: String
    }
    

    он также генерирует код маршалинга без зависимостей, чтобы уговорить возвращаемое значение JSONSerialization.jsonObject на Forecast, включая конструктор удобства, который принимает строку JSON, чтобы вы могли быстро проанализировать строго типизированный Forecast значение и доступ к его полям:

    let forecast = Forecast.from(json: jsonString)!
    print(forecast.daily.data[0].windGustTime)
    

    вы можете установить quicktype из npm с npm i -g quicktype или используйте веб-интерфейс чтобы получить полный сгенерированный код для вставки на детской площадке.

    проблема заключается в методе взаимодействия API.Синтаксический анализ JSON изменяется только в синтаксисе. Основная проблема заключается в способе извлечения данных. Что вы используете синхронный способ передачи данных. Это не работает в каждом случае. Что вы должны использовать асинхронный способ получения данных. Таким образом, вы должны запросить данные через API и ждать, пока он ответит данными. Вы можете достичь этого с помощью сеанса URL и сторонних библиотек, таких как Alamofire. Ниже приведен код для Метод сеанс URL-адрес.

    let urlString = "https://api.forecast.io/forecast/apiKey/37.5673776,122.048951"
    
    let url = URL.init(string: urlString)
    URLSession.shared.dataTask(with:url!) { (data, response, error) in
      guard error == nil else {
      print(error)
      }
      do {
    
        let Data = try JSONSerialization.jsonObject(with: data!) as! [String:Any] // Note if your data is coming in Array you should be using [Any]()
        //Now your data is parsed in Data variable and you can use it normally
    
        let currentConditions = Data["currently"] as! [String:Any]
    
        print(currentConditions)
    
        let currentTemperatureF = currentConditions["temperature"] as! Double
        print(currentTemperatureF)
      } catch let error as NSError {
        print(error)
    
      }
    
    }.resume()