Tout d'abord, ne chargez jamais les données de manière synchrone à partir d'une URL distante , utilisez toujours des méthodes asynchrones comme URLSession
.
'Any' n'a pas de membres en indice
se produit parce que le compilateur n'a aucune idée du type des objets intermédiaires (par exemple currently
dans ["currently"]!["temperature"]
) et que vous utilisez des types de collection Foundation comme NSDictionary
le compilateur n'a aucune idée du type.
De plus, dans Swift 3, il est nécessaire d'informer le compilateur du type de tous les objets en indice.
Vous devez convertir le résultat de la sérialisation JSON en type réel.
Ce code utilise URLSession
et exclusivement des types natifs 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()
Pour imprimer toutes les paires clé / valeur, currentConditions
vous pouvez écrire
let currentConditions = parsedData["currently"] as! [String:Any]
for (key, value) in currentConditions {
print("\(key) - \(value) ")
}
Une note concernant jsonObject(with data
:
De nombreux (il semble tout) des tutoriels suggèrent .mutableContainers
ou des .mutableLeaves
options qui est tout à fait non - sens dans Swift. Les deux options sont des options Objective-C héritées pour affecter le résultat aux NSMutable...
objets. Dans Swift, tout var
iable est mutable par défaut et le fait de passer l'une de ces options et d'assigner le résultat à une let
constante n'a aucun effet. De plus, la plupart des implémentations ne font jamais muter le JSON désérialisé de toute façon.
La seule option (rare) qui est utile dans Swift est ce .allowFragments
qui est nécessaire si si l'objet racine JSON pourrait être un type de valeur ( String
, Number
, Bool
ou null
) au lieu de l' un des types de collecte ( array
ou dictionary
). Mais normalement, omettez le options
paramètre qui signifie Aucune option .
=================================================== ==========================
Quelques considérations générales pour analyser JSON
JSON est un format de texte bien organisé. Il est très facile de lire une chaîne JSON. Lisez attentivement la chaîne . Il n'y a que six types différents: deux types de collection et quatre types de valeur.
Les types de collection sont
- Array - JSON: objets entre crochets
[]
- Swift: [Any]
mais dans la plupart des cas[[String:Any]]
- Dictionnaire - JSON: objets entre accolades
{}
- Swift:[String:Any]
Les types de valeur sont
- Chaîne - JSON: toute valeur entre guillemets
"Foo"
, paire "123"
ou "false"
- Swift:String
- Number - JSON: valeurs numériques non entre guillemets
123
ou 123.0
- Swift: Int
ouDouble
- Bool - JSON:
true
ou false
pas entre guillemets - Swift: true
oufalse
- null - JSON:
null
- Swift:NSNull
Selon la spécification JSON, toutes les clés des dictionnaires doivent être String
.
En gros, il est toujours recommandé d'utiliser des liaisons optionnelles pour dérouler les options en toute sécurité
Si l'objet racine est un dictionnaire ( {}
), transtypez le type en[String:Any]
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [String:Any] { ...
et récupérez les valeurs par clés avec ( OneOfSupportedJSONTypes
est une collection JSON ou un type de valeur comme décrit ci-dessus.)
if let foo = parsedData["foo"] as? OneOfSupportedJSONTypes {
print(foo)
}
Si l'objet racine est un tableau ( []
) transtypez le type en[[String:Any]]
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]] { ...
et parcourez le tableau avec
for item in parsedData {
print(item)
}
Si vous avez besoin d'un élément à un index spécifique, vérifiez également si l'index existe
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]], parsedData.count > 2,
let item = parsedData[2] as? OneOfSupportedJSONTypes {
print(item)
}
}
Dans le cas rare où le JSON est simplement l'un des types de valeur - plutôt qu'un type de collection - vous devez passer l' .allowFragments
option et convertir le résultat dans le type de valeur approprié, par exemple
if let parsedData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? String { ...
Apple a publié un article complet dans le blog Swift: Travailler avec JSON dans Swift
=================================================== ==========================
Dans Swift 4+, le Codable
protocole fournit un moyen plus pratique d'analyser JSON directement en structures / classes.
Par exemple, l'exemple JSON donné dans la question (légèrement modifié)
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}
"""
peut être décodé dans la structure Weather
. Les types Swift sont les mêmes que ceux décrits ci-dessus. Il existe quelques options supplémentaires:
- Les chaînes représentant un
URL
peuvent être décodées directement comme URL
.
- L'
time
entier peut être décodé comme Date
avec le dateDecodingStrategy
.secondsSince1970
.
- Les clés JSON snaked_cased peuvent être converties en camelCase avec
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)
}
Autres sources codables: