114 votes

Analyser correctement JSON dans Swift 3

Je suis en train de chercher une réponse JSON et stocker le résultat dans une variable. J'ai eu des versions de ce code de travail dans les versions précédentes de Swift, jusqu'à ce que le GM version de Xcode 8 a été publié. J'ai eu un coup d'oeil à quelques-uns des postes similaires sur StackOverflow: Swift 2 Parsing JSON Ne peut pas l'indice d'une valeur de type 'AnyObject' et JSON l'Analyse en Swift 3.

Cependant, il semble que les idées véhiculées il ne s'applique pas dans ce scénario.

Comment dois-je analyser correctement le JSON reponse rapide 3? A quelque chose de changé dans la manière de JSON est lu en Swift 3?

Ci-dessous est le code en question (il peut être utilisé dans une aire 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]

161voto

vadian Points 29149

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 pas d'indice membres

se produit parce que le compilateur n'a aucune idée de ce type les objets intermédiaires sont (par exemple, currently en ["currently"]!["temperature"]) et puisque vous êtes à l'aide de la Fondation de la collecte des types comme NSDictionary le compilateur n'a pas d'idée sur le type.

En outre, dans Swift 3 il est tenu d'en informer le compilateur sur le type de tous les indicée objets.

Vous devez lancer le résultat de la sérialisation JSON pour le type réel.

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 toutes les paires clé / valeur de currentConditions vous pouvez écrire

 let currentConditions = parsedData["currently"] as! [String:Any]

  for (key, value) in currentConditions {
    print("\(key) - \(value) ")
  }

Une remarque concernant l' jsonObject(with data:

De nombreux (il semble) tutoriels suggèrent .mutableContainers ou .mutableLeaves options qui est complètement absurde en Swift. Les deux options sont hérités Objective-C d'affecter le résultat d' NSMutable... objets. Dans Swift tout variable est mutable par défaut et en passant de l'une de ces options et en assignant le résultat d'un let de la constante n'a pas d'effet du tout. En outre, la plupart des implémentations ne sont jamais la mutation de la désérialisé JSON de toute façon.

Le seul (rare) option qui est utile dans la Swift est - .allowFragments ce qui est nécessaire si si le JSON de la racine de l'objet peut être un type de la valeur(String, Number, Bool ou null) plutôt qu'un des types de collection (array ou dictionary). Mais normalement omettre l' options paramètre qui signifie Pas d'options.

===========================================================================

Quelques considérations d'ordre général pour parser JSON

JSON est un bien agencé, le format de texte. Il est très facile de lire une chaîne JSON. Lire la chaîne soigneusement. Il y a seulement six types différents – deux types de collection et de quatre types de valeur.


Les types de collection sont

  • Tableau - JSON: les 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

  • Chaîne - JSON: toute valeur entre guillemets doubles "Foo", même "123"ou "false" – Swift: String
  • Nombre - JSON: les valeurs numériques sont pas des guillemets, 123 ou 123.0 – Swift: Int ou Double
  • Bool - JSON: true ou false pas de guillemets doubles – Swift: true ou false
  • null - JSON: null – Swift: NSNull

Selon le JSON spécification de toutes les clés dans les dictionnaires doivent être String.


Fondamentalement, il est toujours recommandé d'utiliser en option pour les liaisons de déballer les options en toute sécurité

Si l'objet racine est un dictionnaire ({}) en fonte le type d' [String:Any]

if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [String:Any] { ...

et de récupérer les valeurs par touches (OneOfSupportedJSONTypes est soit JSON de la collection ou de la valeur type décrit ci-dessus.)

if let foo = parsedData["foo"] as? OneOfSupportedJSONTypes {
    print(foo)
} 

Si la racine de l'objet est un tableau ([]) en fonte le type d' [[String:Any]]

if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]] { ...

et de parcourir le tableau avec

for item in parsedData {
    print(item)
}

Si vous avez besoin d'un élément à l'indice de vérifier également si l'index n'existe

if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]], parsedData.count > 2,
   let item = parsedData[2] as? OneOfSupportedJSONTypes {
      print(item)
    }
}

Dans les rares cas où le JSON est tout simplement l'un des types de valeur – plutôt que d'un type de collection – vous devrez passer à l' .allowFragments option et jeté le résultat à la valeur appropriée, par exemple de type

if let parsedData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? String { ...

Apple a publié un article dans la Swift Blog: Travailler avec JSON en Swift

Mise à jour: Swift 4+ Codable protocole fournit un moyen plus pratique pour parser JSON directement dans les structures / classes.

12voto

discorevilo Points 138

Un grand changement qui s'est produit avec Xcode Beta 8 6 for Swift 3 était qu'une identification des importations de l' Any plutôt que d' AnyObject.

Cela signifie qu' parsedData est retourné comme un dictionnaire de plus probable avec le type [Any:Any]. Sans l'aide d'un débogueur je ne pourrais pas vous dire exactement ce que votre plâtre pour NSDictionary vais faire, mais l'erreur que vous voyez est parce qu' dict!["currently"]! type Any

Alors, comment vous résoudre ce problème? De la façon dont vous faites référence, je suppose dict!["currently"]! est un dictionnaire et si vous avez de nombreuses options:

D'abord, vous pourriez faire quelque chose comme ceci:

let currentConditionsDictionary: [String: AnyObject] = dict!["currently"]! as! [String: AnyObject]  

Cela vous donnera un dictionnaire d'objet que vous pouvez ensuite d'interroger les valeurs et si 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 cela 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, peut-être simplement jeté la charge utile JSON en [String: AnyObject] dès le début.

let parsedData = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments) as! Dictionary<String, AnyObject>

6voto

BhuShan PaWar Points 79
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)")
}

4voto

David Siegel Points 140

J'ai construit quicktype exactement à cette fin. Il suffit de coller votre échantillon JSON et quicktype génère ce type de hiérarchie pour votre API de données:

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 de la dépendance gratuit code de marshaling de l'amadouer la valeur de retour de l' JSONSerialization.jsonObject en Forecast, y compris une commodité constructeur qui prend une chaîne JSON de sorte que vous pouvez analyser rapidement une fortement typé Forecast de la valeur et de l'accès à ses champs:

let forecast = Forecast.from(json: jsonString)!
print(forecast.daily.data[0].windGustTime)

Vous pouvez installer quicktype de mnp avec npm i -g quicktype ou l'utilisation de l'INTERFACE web pour obtenir le code généré pour la coller dans votre aire de jeux.

3voto

Marco Weber Points 292

Mis à jour la fonction isConnectToNetwork par la suite, grâce à cet article. Vérifiez la connexion Internet avec Swift

J'ai écrit une méthode supplémentaire pour cela:

 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) {
        $0.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

}
 

Alors maintenant, vous pouvez facilement appeler cela 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
                }
  }
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by: