20

The problem is when there is incomplete data NSJSONSerialization.JSONObjectWithData is crashing the application giving unexpectedly found nil while unwrapping an Optional value error instead of informing us using NSError variable. So we are unable to prevent crash.

You can find code we are using below

      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") }

Till now we are unable to find a work around.

duggu
  • 37,851
  • 12
  • 116
  • 113
Hope
  • 2,096
  • 3
  • 23
  • 40

5 Answers5

48

Updated for 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)")
}
LightningStryk
  • 920
  • 8
  • 11
  • 5
    ouch swift really has an awful syntax compared to objc. "let", "do", "try", "guard", "as?", "catch"...so much jargon. – malhal Nov 24 '15 at 00:50
  • It would be way better if you could just catch the runtime exception of it failing to cast to the NSDictionary, can't believe they had to add a whole new keyword guard just to make this possible. – malhal Nov 24 '15 at 07:40
  • Using capital first letters for variable names is a bad idea, you are confusing people. But at the same time, thanks for this answer. – Lukas Petr Jun 25 '16 at 10:26
  • If the variable is an acronym (e.g. JSON, URL, etc.) I prefer to capitalize the whole word, otherwise it looks odd (jSON, uRL) – LightningStryk Jul 05 '16 at 23:06
25

The problem is that you cast the result of the JSON deserialization before checking for an error. If the JSON data is invalid (e.g. incomplete) then

NSJSONSerialization.JSONObjectWithData(...)

returns nil and

NSJSONSerialization.JSONObjectWithData(...) as NSDictionary

will crash.

Here is a version that checks for the error conditions correctly:

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!)")
}

Remarks:

  • The correct way to check for an error is to test the return value, not the error variable.
  • The JSON reading option .AllowFragments does not help here. Setting this option only allows that top-level objects that are not an instance of NSArray or NSDictionary, for example

    { "someString" }
    

You can also do it in one line, with an optional cast as?:

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

The disadvantage is that in the else case you cannot distinguish whether reading the JSON data failed or if the JSON did not represent a dictionary.

For an update to Swift 3, see LightningStryk's answer.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • The part you explained as "Here is a version that checks for the error conditions correctly" is not catching the error either. It is crushed at the line unfortunately let dataToUse: AnyObject = NSJSONSerialization.JSONObjectWithData(receivedData, options: NSJSONReadingOptions.AllowFragments, error:&error)! I'd like the say that this and previous codes I submitted works just fine in case of there is no problem with the received data. – Hope Jan 13 '15 at 07:31
  • @Hope: That is not exactly what I suggested. If you forcefully unwrap the result with `!` then you have the same problem as with the forced cast `as NSDictionary`: It will crash if the result is nil. – Martin R Jan 13 '15 at 07:48
  • let dataToUse: AnyObject? = NSJSONSerialization.JSONObjectWithData(receivedData, options: NSJSONReadingOptions.AllowFragments, error:&error) Seems to be OK. So I am checking question as answered by you. At least your grasp about the subject is better. – Hope Jan 13 '15 at 11:47
2

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)")
}
Ashok R
  • 19,892
  • 8
  • 68
  • 68
0

Here is a Swift 2 extension you can use to deserialise only an 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;
    }
}

Sorry I wasn't sure how to do a guard return to avoid creating the temporary 'd'.

malhal
  • 26,330
  • 7
  • 115
  • 133
0

Swift 3 NSJSONSerialization sample (read json from file):

file data.json (example from here: 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"
        }
    }
}
}
}

file 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)")
            }

        }
    }
}

Result (log screenshot)

enter image description here

Vasily Bodnarchuk
  • 24,482
  • 9
  • 132
  • 127