Prima di tutto non caricare mai i dati in modo sincrono da un URL remoto , usa sempre metodi asincroni come URLSession
.
"Qualsiasi" non ha membri in pedice
si verifica perché il compilatore non ha idea di quale tipo siano gli oggetti intermedi (ad esempio currently
in ["currently"]!["temperature"]
) e poiché si utilizzano tipi di raccolta Foundation come NSDictionary
il compilatore non ha idea del tipo.
Inoltre in Swift 3 è necessario informare il compilatore sul tipo di tutti gli oggetti sottoscritti.
Devi eseguire il cast del risultato della serializzazione JSON sul tipo effettivo.
Questo codice utilizza URLSession
e esclusivamente Swift tipi nativi
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()
Per stampare tutte le coppie chiave / valore di currentConditions
te potresti scrivere
let currentConditions = parsedData["currently"] as! [String:Any]
for (key, value) in currentConditions {
print("\(key) - \(value) ")
}
Una nota riguardante jsonObject(with data
:
Molti tutorial (sembrano tutti) suggeriscono .mutableContainers
o .mutableLeaves
opzioni che non hanno alcun senso in Swift. Le due opzioni sono opzioni legacy Objective-C per assegnare il risultato agli NSMutable...
oggetti. In Swift, qualsiasi var
iable è modificabile per impostazione predefinita e passare una qualsiasi di queste opzioni e assegnare il risultato a una let
costante non ha alcun effetto. Inoltre, la maggior parte delle implementazioni non modifica mai comunque il JSON deserializzato.
L'unico (raro) opzione che è utile in Swift è .allowFragments
che è necessario se se l'oggetto principale JSON potrebbe essere un tipo di valore ( String
, Number
, Bool
o null
) piuttosto che uno dei tipi di raccolta ( array
o dictionary
). Ma normalmente ometti il options
parametro che significa Nessuna opzione .
================================================== =========================
Alcune considerazioni generali per analizzare JSON
JSON è un formato di testo ben organizzato. È molto facile leggere una stringa JSON. Leggi attentamente la stringa . Esistono solo sei tipi diversi: due tipi di raccolta e quattro tipi di valore.
I tipi di raccolta sono
- Array - JSON: oggetti tra parentesi quadre
[]
- Swift: [Any]
ma nella maggior parte dei casi[[String:Any]]
- Dizionario - JSON: oggetti tra parentesi graffe
{}
- Swift:[String:Any]
I tipi di valore sono
- String - JSON: qualsiasi valore tra virgolette doppie
"Foo"
, pari "123"
o "false"
- Swift:String
- Number - JSON: valori numerici non tra virgolette
123
o 123.0
- Swift: Int
oDouble
- Bool - JSON:
true
o false
non tra virgolette doppie - Swift: true
ofalse
- null - JSON:
null
- Swift:NSNull
Secondo la specifica JSON, tutte le chiavi nei dizionari devono essere String
.
Fondamentalmente si consiglia sempre di utilizzare attacchi opzionali per scartare gli optional in modo sicuro
Se l'oggetto radice è un dictionary ( {}
), esegui il cast del tipo a[String:Any]
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [String:Any] { ...
e recuperare i valori tramite chiavi con ( OneOfSupportedJSONTypes
è una raccolta JSON o un tipo di valore come descritto sopra).
if let foo = parsedData["foo"] as? OneOfSupportedJSONTypes {
print(foo)
}
Se l'oggetto radice è un array ( []
), esegue il cast del tipo a[[String:Any]]
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]] { ...
e scorrere l'array con
for item in parsedData {
print(item)
}
Se hai bisogno di un elemento in un indice specifico, controlla anche se l'indice esiste
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]], parsedData.count > 2,
let item = parsedData[2] as? OneOfSupportedJSONTypes {
print(item)
}
}
Nel raro caso in cui JSON sia semplicemente uno dei tipi di valore, piuttosto che un tipo di raccolta, è necessario passare l' .allowFragments
opzione e trasmettere il risultato al tipo di valore appropriato, ad esempio
if let parsedData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? String { ...
Apple ha pubblicato un articolo completo nel blog Swift: Working with JSON in Swift
================================================== =========================
In Swift 4+ il Codable
protocollo fornisce un modo più conveniente per analizzare JSON direttamente in strutture / classi.
Ad esempio il dato campione JSON nella domanda (leggermente modificato)
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}
"""
può essere decodificato nella struttura Weather
. I tipi Swift sono gli stessi descritti sopra. Ci sono alcune opzioni aggiuntive:
- Le stringhe che rappresentano un
URL
possono essere decodificate direttamente come URL
.
- L'
time
intero può essere decodificato come Date
con il dateDecodingStrategy
.secondsSince1970
.
- Le chiavi JSON snaked_cased possono essere convertite in camelCase con l' estensione
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)
}
Altre fonti codificabili: