在Swift 3中正确解析JSON
在Swift 3中正确解析JSON
我试图获取一个JSON响应并将结果存储在一个变量中。在之前的Swift版本中,这段代码可以正常工作,直到Xcode 8的GM版本发布。我看了一下StackOverflow上的一些类似帖子:Swift 2 Parsing JSON - Cannot subscript a value of type \'AnyObject\'和JSON Parsing in Swift 3。然而,这些帖子中的思想似乎不适用于这种情况。在Swift 3中,如何正确解析JSON响应?在Swift 3中读取JSON的方式有所改变吗?以下是相关代码(可以在playground中运行):\nimport Cocoa\nlet url = \"https://api.forecast.io/forecast/apiKey/37.5673776,122.048951\"\nif let url = NSURL(string: url) {\n if let data = try? Data(contentsOf: url as URL) {\n do {\n let parsedData = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments)\n //将响应存储在NSDictionary中以便于访问\n let dict = parsedData as? NSDictionary\n let currentConditions = \"\\(dict![\"currently\"]!)\"\n //这会产生一个错误,类型\'Any\'没有下标成员\n let currentTemperatureF = (\"\\(dict![\"currently\"]![\"temperature\"]!!)\" as NSString).doubleValue\n //显示来自API的所有当前状态\n print(currentConditions)\n //以华氏温度输出当前温度\n print(currentTemperatureF)\n }\n //否则抛出一个详细描述出错原因的错误\n catch let error as NSError {\n print(\"JSON解析错误的详细信息:\\n \\(error)\")\n }\n }\n}\n
\n编辑:以下是API调用后的结果样例(在print(currentConditions)
后):\n[\"icon\": partly-cloudy-night, \"precipProbability\": 0, \"pressure\": 1015.39, \"humidity\": 0.75, \"precipIntensity\": 0, \"windSpeed\": 6.04, \"summary\": Partly Cloudy, \"ozone\": 321.13, \"temperature\": 49.45, \"dewPoint\": 41.75, \"apparentTemperature\": 47, \"windBearing\": 332, \"cloudCover\": 0.28, \"time\": 1480846460]
在Swift 3中正确解析JSON的问题出现的原因是使用`JSONSerialization.jsonObject(with:options:)`方法时,返回的是`Any`类型而不是原始的JSON对象类型。这导致了在访问JSON对象的属性时需要进行强制类型转换。
为了解决这个问题,可以使用可选绑定来检查JSON对象的属性是否存在,并将其转换为正确的类型。在上面的例子中,可以使用可选绑定来检查`json["names"]`是否存在,并将其转换为[String]类型。如果存在,即可打印出names的值。
修复后的代码如下:
let str = "{\"names\": [\"Bob\", \"Tim\", \"Tina\"]}"
let data = str.data(using: String.Encoding.utf8, allowLossyConversion: false)!
do {
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: AnyObject] {
if let names = json["names"] as? [String] {
print(names)
}
}
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
这样就可以正确解析JSON并访问其属性了。
在Swift 3中,Xcode 8 Beta 6的一个重大变化是id现在被导入为Any
而不是AnyObject
。
这意味着parsedData
返回的是一个字典,类型很可能是[Any:Any]
。没有使用调试器,我无法告诉你将dict
强制转换为NSDictionary
会发生什么,但你看到的错误是因为dict!["currently"]!
的类型是Any
。
那么,你如何解决这个问题?根据你的引用方式,我假设dict!["currently"]!
是一个字典,所以你有很多选择:
首先,你可以这样做:
let currentConditionsDictionary: [String: AnyObject] = dict!["currently"]! as! [String: AnyObject]
这将给你一个字典对象,然后你可以查询值,这样你就可以这样获取温度:
let currentTemperatureF = currentConditionsDictionary["temperature"] as! Double
或者,如果你愿意,你可以在一行内完成:
let currentTemperatureF = (dict!["currently"]! as! [String: AnyObject])["temperature"]! as! Double
希望这可以帮到你,恐怕我没有时间编写一个示例应用程序来测试它。
最后一点:最简单的方法可能是在开始时将JSON负载转换为[String: AnyObject]
。
let parsedData = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments) as! Dictionary<String, AnyObject>
dict!["currently"]! as! [String: String]
会导致崩溃。
它崩溃的点是["temperature"]!!还有试图将String强制转换为NSString-验证了新的解决方法swiftlang.ng.bluemix.net/#/repl/57d3bc683a422409bf36c391。
[String: String]
根本不会起作用,因为有几个数值。它在Mac Playground中不起作用。
啊,是的,我没有注意到这些,而且Swift沙箱还很贴心地将它们隐藏了!-现在已经更正[再次](并在macOS上进行了测试)。感谢你指出这一点:)
在Swift 3中正确解析JSON的问题出现的原因是编译器对中间对象的类型没有了解(例如`["currently"]!["temperature"]`中的`currently`),由于使用了Foundation集合类型如`NSDictionary`,编译器对类型完全没有了解。
解决方法是将JSON序列化的结果转为实际类型。
下面是解决方法的代码示例:
let urlString = "https://api.forecast.io/forecast/apiKey/37.5673776,122.048951"
let url = URL(string: urlString)
URLSession.shared.dataTask(with:url!) { (data, response, error) in
if error != nil {
print(error)
} else {
do {
let parsedData = try JSONSerialization.jsonObject(with: data!) as! [String:Any]
let currentConditions = parsedData["currently"] as! [String:Any]
print(currentConditions)
let currentTemperatureF = currentConditions["temperature"] as! Double
print(currentTemperatureF)
} catch let error as NSError {
print(error)
}
}
}.resume()
另外,为了打印出`currentConditions`的所有key/value对,可以使用以下代码:
let currentConditions = parsedData["currently"] as! [String:Any]
for (key, value) in currentConditions {
print("\(key) - \(value)")
}
关于`jsonObject(with data)`的一个说明:
很多教程都建议使用`mutableContainers`或`mutableLeaves`选项,但在Swift中这完全没有意义。这两个选项是Objective-C的遗留选项,用于将结果分配给`NSMutable...`对象。在Swift中,任何`var`变量默认都是可变的,而且对于那些从未改变反序列化的JSON的实现,将结果分配给`let`常量根本没有任何效果。在Swift中,唯一(少见的)有用的选项是`allowFragments`,如果JSON的根对象可以是值类型(如`String`,`Number`,`Bool`或`null`)而不是集合类型(`array`或`dictionary`),则需要使用该选项。但通常省略`options`参数,即表示“没有选项”。
一些解析JSON的一般性考虑:
JSON是一种格式良好的文本格式,很容易阅读JSON字符串。JSON只有六种不同的类型,两种集合类型和四种值类型。
集合类型是:
- `Array` - JSON中用方括号`[]`表示,Swift中使用`[Any]`,但在大多数情况下使用`[[String:Any]]`。
- `Dictionary` - JSON中用花括号`{}`表示,Swift中使用`[String:Any]`。
值类型是:
- `String` - JSON中用双引号括起来的任何值,如`"Foo"`,甚至`"123"`或`"false"`,在Swift中使用`String`类型。
- `Number` - JSON中的数字值,不用双引号括起来,如`123`或`123.0`,在Swift中使用`Int`或`Double`类型。
- `Bool` - JSON中的`true`或`false`,不用双引号括起来,在Swift中使用`true`或`false`。
- `null` - JSON中的`null`,在Swift中使用`NSNull`。
根据JSON规范,字典中的所有键都必须是`String`类型。
对于根对象是字典(`{}`)的情况,可以将类型转为`[String:Any]`,并通过键来获取值。示例代码如下:
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [String:Any] {
if let foo = parsedData["foo"] as? OneOfSupportedJSONTypes {
print(foo)
}
}
对于根对象是数组(`[]`)的情况,可以将类型转为`[[String:Any]]`,并通过循环遍历数组。示例代码如下:
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]] {
for item in parsedData {
print(item)
}
}
在很少的情况下,JSON只是一个值类型,而不是集合类型。这种情况下,需要传递`.allowFragments`选项,并将结果转为适当的值类型。示例代码如下:
if let parsedData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? String {
// Handle the string value
}
在Swift 4+中,可以使用`Codable`协议更方便地将JSON直接解析为结构体/类。示例代码如下:
struct Weather: Decodable {
let icon, summary: String
let pressure: Double, humidity, windSpeed: Double
let ozone, temperature, dewPoint, cloudCover: Double
let precipProbability, precipIntensity, apparentTemperature, windBearing: Int
let time: Date
}
let jsonString = """
{"icon": "partly-cloudy-night", "precipProbability": 0, "pressure": 1015.39, "humidity": 0.75, "precip_intensity": 0, "wind_speed": 6.04, "summary": "Partly Cloudy", "ozone": 321.13, "temperature": 49.45, "dew_point": 41.75, "apparent_temperature": 47, "wind_bearing": 332, "cloud_cover": 0.28, "time": 1480846460}
"""
let data = Data(jsonString.utf8)
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .secondsSince1970
decoder.keyDecodingStrategy = .convertFromSnakeCase
let result = try decoder.decode(Weather.self, from: data)
print(result)
} catch {
print(error)
}
在Swift 3中,可能需要使用`JSONSerialization.ReadingOptions.mutableContainers`选项,而不是空数组作为选项参数。另外,为了安全地解包可选值,建议始终使用可选绑定。
异步连接非常重要,因为同步调用会阻塞调用者(应用程序)直到接收到响应,导致应用程序看起来像是挂起了。
以上就是解析JSON的问题的原因和解决方法。希望对你有帮助!