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 patio de recreo):
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ésprint(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]
Respuestas:
En primer lugar, nunca cargue datos sincrónicamente desde una URL remota , use métodos siempre asíncronos como
URLSession
.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, comoNSDictionary
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 SwiftPara imprimir todos los pares clave / valor de
currentConditions
usted podría escribirUna 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 losNSMutable...
objetos. En Swift, cualquiervar
iable es mutable por defecto y pasar cualquiera de esas opciones y asignar el resultado a unalet
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
onull
) en lugar de uno de los tipos de colección (array
odictionary
). Pero normalmente omite eloptions
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
[]
- Swift:[Any]
pero en la mayoría de los casos[[String:Any]]
{}
- Swift:[String:Any]
Los tipos de valor son
"Foo"
, incluso"123"
o"false"
- Swift:String
123
o123.0
- Swift:Int
oDouble
true
ofalse
no entre comillas dobles - Swift:true
ofalse
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]
y recuperar valores mediante claves con (
OneOfSupportedJSONTypes
es una colección JSON o un tipo de valor como se describe anteriormente).Si el objeto raíz es una matriz (
[]
), envíe el tipo a[[String:Any]]
e iterar a través de la matriz con
Si necesita un elemento en un índice específico, verifique también si el índice existe
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 ejemploApple 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)
se puede decodificar en la estructura
Weather
. Los tipos Swift son los mismos que los descritos anteriormente. Hay algunas opciones adicionales:URL
se pueden decodificar directamente comoURL
.time
entero se puede decodificar comoDate
con eldateDecodingStrategy
.secondsSince1970
.keyDecodingStrategy
.convertFromSnakeCase
Otras fuentes codificables:
fuente
dict!["currently"]!
a un diccionario que el compilador puede inferir de manera segura la subsiguiente suscripción de clave.Un gran cambio que sucedió con Xcode 8 Beta 6 para Swift 3 fue que la identificación ahora importa como en
Any
lugar deAnyObject
.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éNSDictionary
hará tu elenco , pero el error que estás viendo es porquedict!["currently"]!
tiene tipoAny
Entonces, ¿cómo resuelves esto? Por la forma en que lo ha referenciado, supongo que
dict!["currently"]!
es un diccionario, por lo que tiene muchas opciones:Primero podrías hacer algo como esto:
Esto le dará un objeto de diccionario que luego puede consultar para valores y así puede obtener su temperatura de esta manera:
O si lo prefiere, puede hacerlo en línea:
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
[String: AnyObject]
al principio.fuente
dict!["currently"]! as! [String: String]
se estrellará[String: String]
no puede funcionar en absoluto porque hay un par de valores numéricos. No funciona en un Mac Playgroundfuente
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:
También genera un código de clasificación libre de dependencias para obtener el valor de retorno de
JSONSerialization.jsonObject
aForecast
, incluido un constructor de conveniencia que toma una cadena JSON para que pueda analizar rápidamente unForecast
valor fuertemente tipado y acceder a sus campos:Puede instalar quicktype desde npm con
npm i -g quicktype
o usar la interfaz de usuario web para obtener el código generado completo para pegarlo en su patio de recreo.fuente
Actualizado el
isConnectToNetwork-Function
después, gracias a esta publicación .Escribí un método extra para ello:
Así que ahora puedes llamar fácilmente a esto en tu aplicación donde quieras
fuente
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:
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.
fuente
Esta es otra forma de resolver su problema. Por lo tanto, consulte la siguiente solución. Espero que te ayude.
fuente
El problema es con el método de interacción 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ía usar es una forma asincrónica de obtener datos. De esta manera, debe solicitar datos a través de la API y esperar a que responda con datos. Puede lograr esto con sesiones de URL y bibliotecas de terceros como
Alamofire
. A continuación se muestra el código para el método de sesión URL.fuente
Si creo un modelo usando json anterior Usando este enlace [blog]: http://www.jsoncafe.com para generar una estructura codificable o cualquier formato
Modelo
Analizar gramaticalmente
fuente