web-dev-qa-db-fra.com

Swift, NSJSONSérialisation et NSError

Le problème est quand il y a des données incomplètes NSJSONSerialization.JSONObjectWithData plante l'application donnant unexpectedly found nil while unwrapping an Optional value erreur au lieu de nous informer en utilisant la variable NSError. Nous ne pouvons donc pas empêcher un crash.

Vous pouvez trouver le code que nous utilisons ci-dessous

      var error:NSError? = nil

      let dataToUse = NSJSONSerialization.JSONObjectWithData(receivedData, options:   NSJSONReadingOptions.AllowFragments, error:&error) as NSDictionary

    if error != nil { println( "There was an error in NSJSONSerialization") }

Jusqu'à présent, nous ne pouvons pas trouver de solution.

17
Hope

Le problème est que vous convertissez le résultat de la désérialisation JSON avant de rechercher une erreur. Si les données JSON sont invalides (par exemple incomplètes),

NSJSONSerialization.JSONObjectWithData(...)

renvoie nil et

NSJSONSerialization.JSONObjectWithData(...) as NSDictionary

va planter.

Voici une version qui vérifie correctement les conditions d'erreur:

var error:NSError? = nil
if let jsonObject: AnyObject = NSJSONSerialization.JSONObjectWithData(receivedData, options: nil, error:&error) {
    if let dict = jsonObject as? NSDictionary {
        println(dict)
    } else {
        println("not a dictionary")
    }
} else {
    println("Could not parse JSON: \(error!)")
}

Remarques:

  • La bonne façon de vérifier une erreur consiste à tester la valeur de retour , pas la variable d'erreur.
  • L'option de lecture JSON .AllowFragments n'aide pas ici. La définition de cette option autorise uniquement les objets de niveau supérieur qui ne sont pas une instance de NSArray ou NSDictionary, par exemple

    { "someString" }
    

Vous pouvez également le faire sur une seule ligne, avec un cast optionnel as?:

if let dict = NSJSONSerialization.JSONObjectWithData(receivedData, options: nil, error:nil) as? NSDictionary {
    println(dict)
} else {
    println("Could not read JSON dictionary")
}

L'inconvénient est que dans le cas else, vous ne pouvez pas distinguer si la lecture des données JSON a échoué ou si le JSON ne représentait pas un dictionnaire.

Pour une mise à jour de Swift 3, voir réponse de LightningStryk .

24
Martin R

Mis à jour pour Swift 3

let jsonData = Data()
do {
    let jsonObject = try JSONSerialization.jsonObject(with: jsonData, options:JSONSerialization.ReadingOptions(rawValue: 0))
    guard let dictionary = jsonObject as? Dictionary<String, Any> else {
        print("Not a Dictionary")
        // put in function
        return
    }
    print("JSON Dictionary! \(dictionary)")
}
catch let error as NSError {
    print("Found an error - \(error)")
}

Swift 2

let JSONData = NSData()
do {
    let JSON = try NSJSONSerialization.JSONObjectWithData(JSONData, options:NSJSONReadingOptions(rawValue: 0))
    guard let JSONDictionary: NSDictionary = JSON as? NSDictionary else {
        print("Not a Dictionary")
        // put in function
        return
    }
    print("JSONDictionary! \(JSONDictionary)")
}
catch let JSONError as NSError {
    print("\(JSONError)")
}
46
LightningStryk

Swift 3:

let jsonData = Data()
do {
    guard let parsedResult = try JSONSerialization.jsonObject(with: jsonData, options: .allowFragments) as? NSDictionary else {
        return
    }
    print("Parsed Result: \(parsedResult)")
} catch {
    print("Error: \(error.localizedDescription)")
}
1
Ashok R

Voici une extension Swift 2 que vous pouvez utiliser pour désérialiser uniquement un NSDictionary:

extension NSJSONSerialization{
    public class func dictionaryWithData(data: NSData, options opt: NSJSONReadingOptions) throws -> NSDictionary{
        guard let d: NSDictionary = try self.JSONObjectWithData(data, options:opt) as? NSDictionary else{
            throw NSError(domain: NSURLErrorDomain, code: NSURLErrorCannotParseResponse, userInfo: [NSLocalizedDescriptionKey : "not a dictionary"])
        }
        return d;
    }
}

Désolé, je ne savais pas comment faire un retour de garde pour éviter de créer le "d" temporaire.

0
malhal

Exemple Swift 3 NSJSONSerialization (lire json à partir du fichier):

fichier data.json (exemple d'ici: http://json.org/example.html )

{
"glossary":{
"title":"example glossary",
"GlossDiv":{
    "title":"S",
    "GlossList":{
        "GlossEntry":{
            "ID":"SGML",
            "SortAs":"SGML",
            "GlossTerm":"Standard Generalized Markup Language",
            "Acronym":"SGML",
            "Abbrev":"ISO 8879:1986",
            "GlossDef":{
                "para":"A meta-markup language, used to create markup languages such as DocBook.",
                "GlossSeeAlso":[
                                "GML",
                                "XML"
                                ]
            },
            "GlossSee":"markup"
        }
    }
}
}
}

fichier JSONSerialization.Swift

extension JSONSerialization {

enum Errors: Error {
    case NotDictionary
    case NotJSONFormat
}

public class func dictionary(data: Data, options opt: JSONSerialization.ReadingOptions) throws -> NSDictionary {
    do {
        let JSON = try JSONSerialization.jsonObject(with: data , options:opt)
        if let JSONDictionary = JSON as? NSDictionary {
            return JSONDictionary
        }
        throw Errors.NotDictionary
    }
    catch {
        throw Errors.NotJSONFormat
    }
}
}

Usage

 func readJsonFromFile() {
    if let path = Bundle.main.path(forResource: "data", ofType: "json") {
        if let data = NSData(contentsOfFile: path) as? Data {

            do {
                let dict = try JSONSerialization.dictionary(data: data, options: .allowFragments)
                print(dict)
            } catch let error {
                print("\(error)")
            }

        }
    }
}

Résultat (capture d'écran du journal)

enter image description here

0