En primer lugar, nunca cargue datos sincrónicamente desde una URL remota , use métodos siempre asíncronos como URLSession
.
'Cualquiera' 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 exclusivamente tipos nativos 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
usted 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) tutoriales sugieren .mutableContainers
u .mutableLeaves
opciones, lo cual es completamente absurdo en Swift. Las dos opciones son opciones heredadas de Objective-C para asignar el resultado a los NSMutable...
objetos. En Swift, cualquier var
iable es mutable por defecto y pasar cualquiera de esas opciones y asignar el resultado a una let
constante 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 se requiere si 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 options
parámetro 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
oDouble
- Bool - JSON:
true
o false
no entre comillas dobles - Swift: true
ofalse
- nulo - JSON:
null
- Rápido:NSNull
De acuerdo con la especificación JSON, se requiere que todas las claves en los diccionarios estén 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 .allowFragments
opción 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 Codable
protocolo 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 un
URL
se pueden decodificar directamente como URL
.
- El
time
entero se puede decodificar como Date
con el dateDecodingStrategy
.secondsSince1970
.
- Las claves JSON snaked_cased se pueden convertir a camelCase con el
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: