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]
99
demandé sur Community 2016-09-10 09:43:15

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 ou 123.0 – Swift: Int ou Double
  • Bool - JSON: true or false not "1519560920 – entre guillemets-Swift: true ou false
  • 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

146
répondu vadian 2017-06-20 10:12:23

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>
12
répondu discorevilo 2016-09-10 08:09:19
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)")
}
6
répondu BhuShan PaWar 2017-01-24 05:19:51

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
                }
  }
3
répondu Marco Weber 2017-05-23 12:10:41

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.

3
répondu David Siegel 2017-12-30 22:17:05

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()
0
répondu Arun K 2017-10-27 14:16:00