我试图获取一个JSON响应,并将结果存储在一个变量中。我在以前的Swift版本中使用过这个代码版本,直到Xcode8的GM版本发布。我在StackOverflow上看了几篇类似的文章:Swift 2 Parsing JSON-Cant下标类型为“anyObject”的值和Swift 3中的JSON Parsing。
然而,这里所传达的思想似乎不适用于这种情况。
如何正确解析Swift3中的JSON响应?Swift3中读取JSON的方式有什么改变吗?
下面是问题中的代码(可以在操场上运行):
import Cocoa
let url = "https://api.forecast.io/forecast/apiKey/37.5673776,122.048951"
if let url = NSURL(string: url) {
if let data = try? Data(contentsOf: url as URL) {
do {
let parsedData = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments)
//Store response in NSDictionary for easy access
let dict = parsedData as? NSDictionary
let currentConditions = "\(dict!["currently"]!)"
//This produces an error, Type 'Any' has no subscript members
let currentTemperatureF = ("\(dict!["currently"]!["temperature"]!!)" as NSString).doubleValue
//Display all current conditions from API
print(currentConditions)
//Output the current temperature in Fahrenheit
print(currentTemperatureF)
}
//else throw an error detailing what went wrong
catch let error as NSError {
print("Details of JSON parsing error:\n \(error)")
}
}
}
编辑:下面是print(currentConditions)
之后API调用的结果示例
["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]
首先,永远不要从远程URL同步加载数据,始终使用异步方法,如urlsession
。
“any”没有下标成员
发生的原因是编译器不知道中间对象是什么类型(例如[“currentary”]![“temperature”]
中的currentary
),并且由于您使用的是诸如nsdictionary
之类的基础集合类型,编译器根本不知道该类型。
此外,在Swift3中,需要通知编译器所有下标对象的类型。
您必须将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中,默认情况下任何var
iable都是可变的,传递这些选项中的任何一个并将结果赋给let
常量根本不起作用。此外,大多数实现都从未改变反序列化的JSON。
在Swift中唯一有用的(罕见的)选项是.allowfragments
,如果JSON根对象可以是值类型(string
、number
、bool
或null
),而不是集合类型(array
或dictionary
)时,则需要该选项。但通常省略options
参数,这意味着没有选项。
====================================================================================================================================
JSON是一种排列良好的文本格式。读取JSON字符串非常容易。仔细读弦。只有六种不同的类型-- 、两种集合类型和四种值类型。
集合类型为
[]
-swift:[Any]
但大多数情况下[[string:Any]]
{}
-swift:[string:any]
值类型为
“foo”
,甚至“123”
或“false”
-swift:字符串
123
或123.0
-swift:int
或double
true
或false
不在双引号中-swift:true
或false
null
-swift: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 { ...
苹果在Swift博客中发表了一篇全面的文章:在Swift中使用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
。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)
}
其他可编码源: