Swift、NSJSONSerialization和NSError

20

当数据不完整时,NSJSONSerialization.JSONObjectWithData会导致应用程序崩溃,并出现unexpectedly found nil while unwrapping an Optional value错误,而不是使用NSError变量通知我们。因此,我们无法防止崩溃。

以下是我们正在使用的代码

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

至今我们仍未能找到解决方法。

5个回答

48

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

5
与 Objective-C 相比,Swift 的语法确实很糟糕。"let"、"do"、"try"、"guard"、"as?"、"catch" 等术语太多了。 - malhal
如果您能够捕获运行时异常并将其转换为NSDictionary,那将会更好,我无法相信他们不得不添加一个全新的关键字guard才能实现这一点。 - malhal
使用大写字母作为变量名是个糟糕的主意,会使人混淆。不过同时,还是谢谢你的回答。 - Lukas Petr
如果变量是一个首字母缩写词(例如JSON,URL等),我更喜欢把整个单词大写,否则看起来很奇怪(jSON,uRL)。 - LightningStryk

25

问题是您在检查错误之前对JSON反序列化的结果进行了转换。如果JSON数据无效(例如不完整),则

NSJSONSerialization.JSONObjectWithData(...)

返回nil

NSJSONSerialization.JSONObjectWithData(...) as NSDictionary

会崩溃。

这里是一个正确检查错误条件的版本:

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

备注:

  • 检查错误的正确方法是测试返回值而不是错误变量。
  • JSON读取选项.AllowFragments在这里无效。设置此选项仅允许顶级对象不是NSArrayNSDictionary实例,例如

    { "someString" }
    

您还可以使用一行代码完成,使用一个可选值转换as?:

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

缺点在于else情况下,您无法区分是读取JSON数据失败还是JSON未表示为字典。

有关升级到Swift 3的更新,请参见LightningStryk的答案


你所解释的“这里是一个可以正确检查错误条件的版本”并没有捕获错误。它在以下这一行代码崩溃了:let dataToUse: AnyObject = NSJSONSerialization.JSONObjectWithData(receivedData, options: NSJSONReadingOptions.AllowFragments, error:&error)! 我想说的是,只要接收到的数据没有问题,我之前提交的代码都能正常工作。 - Hope
@Hope:这并不是我建议的。如果你使用 ! 强制解包结果,那么你会遇到与强制转换 as NSDictionary 相同的问题:如果结果为 nil,则会崩溃。 - Martin R
让dataToUse: AnyObject? = NSJSONSerialization.JSONObjectWithData(receivedData, options: NSJSONReadingOptions.AllowFragments, error:&error) 看起来没问题。所以我将您的问题标记为已回答。至少您对这个主题的掌握更好了。 - Hope

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

0

这里是一个Swift 2扩展,您可以使用它来仅反序列化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'。


0
Swift 3 NSJSONSerialization示例(从文件中读取json):
文件data.json(来自此处的示例: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"
        }
    }
}
}
}

文件名为 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
    }
}
}

使用方法

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

        }
    }
}

结果(日志截图)

enter image description here


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接