Correctement analyser JSON dans Swift 3
j'essaie de récupérer une réponse JSON et de stocker les résultats dans une variable. J'ai eu des versions de ce code qui fonctionnaient dans les versions précédentes de Swift, jusqu'à ce que la version GM de Xcode 8 soit publiée. J'ai regardé quelques messages similaires sur StackOverflow: Swift 2 Parsing JSON - ne peut pas subscrire une valeur de type 'AnyObject' et JSON Parsing in Swift 3 .
cependant, il semble que les idées qui y sont véhiculées ne s'appliquent pas à cette scénario.
Comment puis-je analyser correctement la réponse de JSON dans Swift 3? Quelque chose a-t-il changé dans la façon dont JSON est lu dans Swift 3?
ci-dessous est le code en question (il peut être exécuté dans un terrain de jeu):
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)")
}
}
}
Edit: Voici un échantillon des résultats de l'appel d'API après 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]
6 réponses
tout d'Abord ne jamais charger les données de façon synchrone à partir d'une URL distante , utilisez toujours des méthodes asynchrones comme URLSession
.
, "Tout" n'a aucun indice membres
se produit parce que le compilateur n'a aucune idée de quel type les objets intermédiaires sont (par exemple currently
dans ["currently"]!["temperature"]
) et puisque vous utilisez des types de collection de fondation comme NSDictionary
le compilateur n'a pas idée sur le type.
de plus, dans Swift 3, Il est nécessaire d'informer le compilateur sur le type d'objets tous les inscrits.
vous devez mouler le résultat de la sérialisation JSON au type actuel.
Ce code utilise URLSession
et exclusivement Swift types natifs
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 tous paires clé / valeur de currentConditions
vous pouvez écrire
let currentConditions = parsedData["currently"] as! [String:Any]
for (key, value) in currentConditions {
print("\(key) - \(value) ")
}
Une note sur jsonObject(with data
:
plusieurs (il semble que tous) tutoriels suggèrent .mutableContainers
ou .mutableLeaves
options qui est complètement absurde dans Swift. Les deux options sont des options objectif-c legacy pour assigner le résultat aux objets NSMutable...
. Dans Swift n'importe quelle var
iable est mutable par défaut et passer l'une de ces options et attribuer le résultat à une constante let
n'a aucun effet. De plus, la plupart des implémentations ne mutent jamais le JSON désérialisé de toute façon.
la seule option (rare) qui est utile dans Swift est .allowFragments
qui est nécessaire si si L'objet racine JSON peut être un type de valeur( String
, Number
, Bool
ou null
) plutôt que l'un des types de collecte ( array
ou dictionary
). Mais normalement omettre le options
paramètre qui signifie pas d'options .
===========================================================================
Quelques considérations d'ordre général pour parser JSON
JSON est un format de texte bien organisé. C'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 collecte et quatre types de valeur.
les types de collecte sont
- Array - JSON: objets entre crochets
[]
- Swift:[Any]
mais dans la plupart des cas[[String:Any]]
- Dictionnaire - JSON: les objets dans des accolades
{}
- Swift:[String:Any]
les types de valeur sont
- String - JSON: toute valeur entre guillemets
"Foo"
, même"123"
ou"false"
– Swift:String
- Nombre - JSON: des valeurs numériques pas entre guillemets doubles
123
ou123.0
– Swift:Int
ouDouble
- Bool - JSON:
true
orfalse
not "1519560920 – entre guillemets-Swift:true
oufalse
- null - JSON: "1519240920 – - Swift:
NSNull
selon la spécification JSON, toutes les clés des dictionnaires doivent être String
.
fondamentalement, il est toujours recommandé d'utiliser des fixations optionnelles pour décompresser optionnelles en toute sécurité
si l'objet racine est un dictionnaire ( {}
) lancer le type à [String:Any]
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [String:Any] { ...
et extraire des valeurs par des touches avec ( OneOfSupportedJSONTypes
est soit la collection JSON ou le type de valeur comme décrit ci-dessus.)
if let foo = parsedData["foo"] as? OneOfSupportedJSONTypes {
print(foo)
}
si l'objet racine est un tableau ( []
) lancer le type à [[String:Any]]
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]] { ...
et itérer à travers le tableau avec
for item in parsedData {
print(item)
}
si vous avez besoin d'un article à l'index spécifique, vérifiez aussi 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 que le JSON est simplement l'un des types de valeur – plutôt qu'un type de collection – vous devez passer l'option .allowFragments
et mouler le résultat au type de valeur approprié par exemple
if let parsedData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? String { ...
Apple a publié un vaste article sur le blog Swift: travailler avec JSON dans Swift
un grand changement qui s'est produit avec Xcode 8 Beta 6 pour Swift 3 était que id importe maintenant comme Any
plutôt que AnyObject
.
cela signifie que parsedData
est retourné comme un dictionnaire très probablement avec le type [Any:Any]
. Sans l'aide d'un débogueur Je ne pouvais pas vous dire exactement ce que votre distribution à NSDictionary
fera, mais l'erreur que vous voyez est parce que dict!["currently"]!
a le type Any
alors, comment résoudre cette? De la façon dont vous l'avez référencé, je suppose que dict!["currently"]!
est un dictionnaire et donc vous avez beaucoup d'options:
vous pourriez D'abord faire quelque chose comme ceci:
let currentConditionsDictionary: [String: AnyObject] = dict!["currently"]! as! [String: AnyObject]
cela vous donnera un objet de dictionnaire que vous pouvez alors demander pour les valeurs et ainsi vous pouvez obtenir votre température comme ceci:
let currentTemperatureF = currentConditionsDictionary["temperature"] as! Double
Ou si vous préférez, vous pouvez le faire en ligne:
let currentTemperatureF = (dict!["currently"]! as! [String: AnyObject])["temperature"]! as! Double
espérons que ceci aide, j'ai peur, je n'ai pas eu le temps d'écrire un exemple d'application pour la tester.
une dernière remarque: la chose la plus facile à faire, pourrait être de simplement lancer la charge utile JSON dans [String: AnyObject]
dès le début.
let parsedData = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments) as! Dictionary<String, AnyObject>
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)")
}
mise à Jour la isConnectToNetwork-Fonction par la suite, grâce à ce post vérifier la connexion Internet avec Swift
j'ai écrit une méthode supplémentaire pour elle:
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) {
"151900920".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
}
donc maintenant vous pouvez facilement l'appeler dans votre application où vous voulez
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
}
}
j'ai construit quicktype exactement à cette fin. Il suffit de coller votre exemple JSON et quicktype génère cette hiérarchie de type pour vos données 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
}
il génère également un code de sélection sans dépendance pour coaxier la valeur de retour de JSONSerialization.jsonObject
dans un Forecast
, y compris un constructeur de commodité qui prend une chaîne JSON de sorte que vous pouvez rapidement analyser une valeur fortement dactylographiée Forecast
et accéder à ses champs:
let forecast = Forecast.from(json: jsonString)!
print(forecast.daily.data[0].windGustTime)
vous pouvez installer quicktype à partir de npm avec npm i -g quicktype
ou utilisez l'UI web pour obtenir le code généré complet à coller dans votre terrain de jeu.
le problème est avec la méthode D'interaction API.L'analyse JSON n'est modifiée qu'en syntaxe. Le principal problème réside dans la manière de récupérer les données. Ce que vous utilisez est un moyen synchrone d'obtenir des données. Cela ne fonctionne pas dans tous les cas. Ce que vous devriez utiliser est une façon asynchrone de récupérer des données. De cette façon, vous devez demander des données via L'API et attendre qu'elle réponde avec des données. Vous pouvez y parvenir avec les sessions URL et les bibliothèques tierces comme Alamofire. Ci-dessous est le Code pour la méthode de Session 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()