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.
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:
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.
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)")
}
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)")
}
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'.
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)