Swift, NSJSONSerialization y NSError
(5)
El problema es cuando hay datos incompletos. NSJSONSerialization.JSONObjectWithData
está fallando en la aplicación, lo que da un resultado unexpectedly found nil while unwrapping an Optional value
error de unexpectedly found nil while unwrapping an Optional value
lugar de informarnos que usamos la variable NSError. Así que somos incapaces de evitar el choque.
Puede encontrar el código que estamos usando a continuación.
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") }
Hasta ahora no podemos encontrar un trabajo alrededor.
Actualizado para 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)")
}
Aquí hay una extensión de Swift 2 que puede usar para deserializar solo 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;
}
}
Lo siento, no estaba seguro de cómo hacer una devolución de guardia para evitar crear la ''d'' temporal.
El problema es que emite el resultado de la deserialización JSON antes de comprobar si hay un error. Si los datos JSON no son válidos (por ejemplo, incompletos), entonces
NSJSONSerialization.JSONObjectWithData(...)
devuelve nil
y
NSJSONSerialization.JSONObjectWithData(...) as NSDictionary
se estrellará
Aquí hay una versión que verifica las condiciones de error correctamente:
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!)")
}
Observaciones:
- La forma correcta de verificar si hay un error es probar el valor de retorno , no la variable de error.
La opción de lectura JSON
.AllowFragments
no ayuda aquí. La configuración de esta opción solo permite que los objetos de nivel superior que no son una instancia deNSArray
oNSDictionary
, por ejemplo{ "someString" }
También puede hacerlo en una línea, con un lanzamiento opcional as?
:
if let dict = NSJSONSerialization.JSONObjectWithData(receivedData, options: nil, error:nil) as? NSDictionary {
println(dict)
} else {
println("Could not read JSON dictionary")
}
La desventaja es que, en el caso else
, no puede distinguir si la lectura de los datos JSON falló o si JSON no representó un diccionario.
Para una actualización a Swift 3, vea la respuesta de LightningStryk .
Swift 3 NSJSONSerialization sample (lea json del archivo):
archivo data.json (ejemplo desde aquí: 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"
}
}
}
}
}
archivo 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
}
}
}
Uso
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)")
}
}
}
}
Resultado (log screenshot)
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)")
}