parser json swift parsing swift3 xcode8

parser - Analizando correctamente JSON en Swift 3



json swift (7)

Construí quicktype exactamente para este propósito. Simplemente pegue su JSON de muestra y quicktype genera esta jerarquía de tipos para sus datos de 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 }

También genera código de JSONSerialization.jsonObject sin dependencia para obtener el valor de retorno de JSONSerialization.jsonObject en un Forecast , incluido un constructor de conveniencia que toma una cadena JSON para que pueda analizar rápidamente un valor de Forecast fuertemente tipado y acceder a sus campos:

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

Puede instalar quicktype desde npm con npm i -g quicktype o usar la interfaz de usuario web para obtener el código completo generado para pegarlo en su área de juegos.

Estoy tratando de obtener una respuesta JSON y almacenar los resultados en una variable. He tenido versiones de este código que funcionan en versiones anteriores de Swift, hasta que se lanzó la versión GM de Xcode 8. Eché un vistazo a algunas publicaciones similares en StackOverflow: Swift 2 Parsing JSON: no se puede subíndice un valor de tipo ''AnyObject'' y JSON Parsing en Swift 3 .

Sin embargo, parece que las ideas transmitidas allí no se aplican en este escenario.

¿Cómo analizo correctamente la respuesta JSON en Swift 3? ¿Ha cambiado algo en la forma en que se lee JSON en Swift 3?

A continuación se muestra el código en cuestión (se puede ejecutar en un parque infantil):

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)") } } }

Editar: Aquí hay una muestra de los resultados de la llamada API después de 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]


El problema está en el método de interacción de la API. El análisis JSON solo se cambia en la sintaxis. El principal problema es con la forma de obtener datos. Lo que está utilizando es una forma síncrona de obtener datos. Esto no funciona en todos los casos. Lo que deberías usar es una forma asincrónica de recuperar datos. De esta manera, debe solicitar datos a través de la API y esperar a que responda con datos. Puede lograr esto con una sesión de URL y bibliotecas de terceros como Alamofire. A continuación se muestra el código para el método de sesión 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()


En primer lugar, nunca cargue datos sincrónicamente desde una URL remota , use métodos siempre asíncronos como URLSession .

''Any'' no tiene miembros de subíndice

se produce porque el compilador no tiene idea de qué tipo son los objetos intermedios (por ejemplo, currently en ["currently"]!["temperature"] ) y dado que está utilizando tipos de colección de Foundation como NSDictionary el compilador no tiene ninguna idea sobre el tipo.

Además, en Swift 3 se requiere informar al compilador sobre el tipo de todos los objetos suscritos.

Debe convertir el resultado de la serialización JSON al tipo real.

Este código usa URLSession y tipos nativos exclusivamente Swift

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()

Para imprimir todos los pares clave / valor de currentConditions , podría escribir

let currentConditions = parsedData["currently"] as! [String:Any] for (key, value) in currentConditions { print("/(key) - /(value) ") }

Una nota sobre jsonObject(with data :

Muchos (parece que todos) los tutoriales sugieren opciones .mutableContainers o .mutableLeaves , lo cual es completamente absurdo en Swift. Las dos opciones son opciones heredadas de Objective-C para asignar el resultado a NSMutable... objetos. En Swift, cualquier variable es mutable por defecto y pasar cualquiera de esas opciones y asignar el resultado a una constante let no tiene ningún efecto. Además, la mayoría de las implementaciones nunca están mutando el JSON deserializado de todos modos.

La única opción (rara) que es útil en Swift es .allowFragments que es necesaria si el objeto raíz JSON podría ser un tipo de valor ( String , Number , Bool o null ) en lugar de uno de los tipos de colección ( array o dictionary ). Pero normalmente omite el parámetro de options , que significa Sin opciones .

================================================== =========================

Algunas consideraciones generales para analizar JSON

JSON es un formato de texto bien organizado. Es muy fácil leer una cadena JSON. Lee la cuerda cuidadosamente . Solo hay seis tipos diferentes: dos tipos de colección y cuatro tipos de valor.

Los tipos de colección son

  • Matriz - JSON: objetos entre corchetes [] - Swift: [Any] pero en la mayoría de los casos [[String:Any]]
  • Diccionario - JSON: objetos entre llaves {} - Swift: [String:Any]

Los tipos de valor son

  • Cadena - JSON: cualquier valor entre comillas dobles "Foo" , incluso "123" o "false" - Swift: String
  • Número - JSON: valores numéricos que no están entre comillas dobles 123 o 123.0 - Swift: Int o Double
  • Bool - JSON: true o false no entre comillas dobles - Swift: true o false
  • nulo - JSON: null - Swift: NSNull

De acuerdo con la especificación JSON, todas las claves en los diccionarios deben ser String .

Básicamente, siempre se recomienda utilizar enlaces opcionales para desenvolver los opcionales de forma segura

Si el objeto raíz es un diccionario ( {} ), envíe el tipo a [String:Any]

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

y recuperar valores mediante claves con ( OneOfSupportedJSONTypes es una colección JSON o un tipo de valor como se describe anteriormente).

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

Si el objeto raíz es una matriz ( [] ), envíe el tipo a [[String:Any]]

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

e iterar a través de la matriz con

for item in parsedData { print(item) }

Si necesita un elemento en un índice específico, verifique también si el índice existe

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

En el raro caso de que JSON sea simplemente uno de los tipos de valor, en lugar de un tipo de colección, debe pasar la opción .allowFragments y emitir el resultado al tipo de valor apropiado, por ejemplo

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

Apple ha publicado un artículo completo en el Blog de Swift: Trabajando con JSON en Swift

================================================== =========================

En Swift 4+, el protocolo Codable proporciona una forma más conveniente de analizar JSON directamente en estructuras / clases.

Por ejemplo, la muestra JSON dada en la pregunta (ligeramente modificada)

let jsonString = """ {"icon": "partly-cloudy-night", "precipProbability": 0, "pressure": 1015.39, "humidity": 0.75, "precip_intensity": 0, "wind_speed": 6.04, "summary": "Partly Cloudy", "ozone": 321.13, "temperature": 49.45, "dew_point": 41.75, "apparent_temperature": 47, "wind_bearing": 332, "cloud_cover": 0.28, "time": 1480846460} """

se puede decodificar en la estructura Weather . Los tipos Swift son los mismos que los descritos anteriormente. Hay algunas opciones adicionales:

  • Las cadenas que representan una URL pueden decodificarse directamente como URL .
  • El entero de time se puede decodificar como Date con dateDecodingStrategy .secondsSince1970 .
  • Las claves JSON snaked_cased se pueden convertir a camelCase con keyDecodingStrategy .convertFromSnakeCase

struct Weather: Decodable { let icon, summary: String let pressure: Double, humidity, windSpeed : Double let ozone, temperature, dewPoint, cloudCover: Double let precipProbability, precipIntensity, apparentTemperature, windBearing : Int let time: Date } let data = Data(jsonString.utf8) do { let decoder = JSONDecoder() decoder.dateDecodingStrategy = .secondsSince1970 decoder.keyDecodingStrategy = .convertFromSnakeCase let result = try decoder.decode(Weather.self, from: data) print(result) } catch { print(error) }

Otras fuentes codificables:


Swift tiene una poderosa inferencia de tipos. Vamos a deshacernos de la placa repetitiva "if let" o "guard let" y forzar el desenvolvimiento utilizando un enfoque funcional:

  1. Aquí está nuestro JSON. Podemos usar JSON opcional o habitual. Estoy usando opcional en nuestro ejemplo:

let json: Dictionary<String, Any>? = ["current": ["temperature": 10]]

  1. Funciones de ayuda. Necesitamos escribirlos solo una vez y luego reutilizarlos con cualquier diccionario:

/// Curry public func curry<A, B, C>(_ f: @escaping (A, B) -> C) -> (A) -> (B) -> C { return { a in { f(a, $0) } } } /// Function that takes key and optional dictionary and returns optional value public func extract<Key, Value>(_ key: Key, _ json: Dictionary<Key, Any>?) -> Value? { return json.flatMap { cast($0[key]) } } /// Function that takes key and return function that takes optional dictionary and returns optional value public func extract<Key, Value>(_ key: Key) -> (Dictionary<Key, Any>?) -> Value? { return curry(extract)(key) } /// Precedence group for our operator precedencegroup RightApplyPrecedence { associativity: right higherThan: AssignmentPrecedence lowerThan: TernaryPrecedence } /// Apply. g § f § a === g(f(a)) infix operator § : RightApplyPrecedence public func §<A, B>(_ f: (A) -> B, _ a: A) -> B { return f(a) } /// Wrapper around operator "as". public func cast<A, B>(_ a: A) -> B? { return a as? B }

  1. Y aquí está nuestra magia: extraer el valor:

let temperature = (extract("temperature") § extract("current") § json) ?? NSNotFound

Solo una línea de código y sin desenredos forzados o fundición manual. Este código funciona en el patio de recreo, por lo que puede copiarlo y verificarlo. Aquí hay una implementación en GitHub.


Un gran cambio que sucedió con Xcode 8 Beta 6 para Swift 3 fue que la identificación ahora se importa como Any lugar de AnyObject .

Esto significa que parsedData se devuelve como un diccionario de lo más probable con el tipo [Any:Any] . Sin usar un depurador, no podría decirte exactamente qué hará tu lanzamiento a NSDictionary , ¡pero el error que estás viendo es porque dict!["currently"]! tiene tipo Any

Entonces, ¿cómo resuelves esto? Por la forma en que lo ha referenciado, supongo que dict!["currently"]! es un diccionario y por eso tienes muchas opciones:

Primero podrías hacer algo como esto:

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

Esto le dará un objeto de diccionario que luego puede consultar para valores y así puede obtener su temperatura de esta manera:

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

O si lo prefiere, puede hacerlo en línea:

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

Espero que esto ayude, me temo que no he tenido tiempo de escribir una aplicación de muestra para probarla.

Una nota final: lo más fácil de hacer, podría ser simplemente lanzar la carga JSON en [String: AnyObject] desde el principio.

let parsedData = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments) as! Dictionary<String, AnyObject>


Actualizó la función isConnectToNetwork después, gracias a esta publicación Compruebe la conexión a Internet con Swift

Escribí un método adicional para ello:

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) { $0.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 }

Así que ahora puedes llamar fácilmente a esto en tu aplicación donde quieras

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 } }


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)") }