首先永远不要从远程 URL 同步加载数据,始终使用异步方法,例如 URLSession。
'Any' 没有下标成员
发生是因为编译器不知道中间对象是什么类型(例如["currently"]!["temperature"] 中的currently),并且由于您使用的是像NSDictionary 这样的Foundation 集合类型,编译器根本不知道该类型.
此外,在 Swift 3 中,需要告知编译器 all 下标对象的类型。
您必须将 JSON 序列化的结果转换为实际类型。
此代码使用URLSession 和独家 Swift 原生类型
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 的所有键/值对,您可以编写
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 中,任何 variable 默认情况下都是可变的,并且传递任何这些选项并将结果分配给 let 常量根本没有任何效果。此外,大多数实现无论如何都不会改变反序列化的 JSON。
在 Swift 中唯一有用的(罕见的)选项是 .allowFragments,如果 JSON 根对象可以是值类型(String、Number、Bool 或 null),则需要此选项比集合类型之一(array 或 dictionary)。但通常省略options 参数,这意味着没有选项。
================================================ ==============================
解析 JSON 的一些一般注意事项
JSON 是一种排列良好的文本格式。读取 JSON 字符串非常容易。 仔细阅读字符串。只有六种不同的类型——两种集合类型和四种值类型。
集合类型是
-
数组 - JSON:方括号中的对象
[] - Swift:[Any] 但在大多数情况下[[String:Any]]
-
字典 - JSON:花括号中的对象
{} - Swift:[String:Any]
值类型是
-
字符串 - JSON:双引号中的任何值
"Foo",甚至是 "123" 或 "false" - Swift:String
-
数字 - JSON:数字值not 用双引号
123 或 123.0 – Swift:Int 或 Double
-
Bool - JSON:
true 或 false not 双引号 - Swift:true 或 false
-
null - JSON:
null - 斯威夫特:NSNull
根据 JSON 规范,字典中的所有键都必须是 String。
基本上总是建议使用可选绑定来安全地展开可选项
如果根对象是字典 ({}),则将类型转换为 [String:Any]
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [String:Any] { ...
并通过键检索值(OneOfSupportedJSONTypes 是 JSON 集合或值类型,如上所述。)
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)
}
如果您需要特定索引处的项目,还要检查索引是否存在
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]], parsedData.count > 2,
let item = parsedData[2] as? OneOfSupportedJSONTypes {
print(item)
}
}
在 JSON 只是一种值类型而不是集合类型的极少数情况下,您必须传递 .allowFragments 选项并将结果转换为适当的值类型,例如
if let parsedData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? String { ...
Apple 在 Swift 博客上发表了一篇综合文章:Working with JSON in Swift
================================================ ==============================
在 Swift 4+ 中,Codable 协议提供了一种更方便的方式来将 JSON 直接解析为结构/类。
例如问题中给定的 JSON 样本(稍作修改)
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}
"""
可以解码为结构Weather。 Swift 类型与上述相同。还有一些其他选项:
- 表示
URL 的字符串可以直接解码为URL。
-
time 整数可以用dateDecodingStrategy .secondsSince1970 解码为Date。
-
snaked_cased JSON 键可以转换为 camelCase 与
keyDecodingStrategy .convertFromSnakeCase
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 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)
}
其他可编码来源: