在Swift 3中正确parsingJSON
我试图获取JSON响应并将结果存储在一个variables中。 我已经在以前的Swift版本中使用过这些代码的版本,直到Xcode 8的GM版本发布。 我看了一下StackOverflow上的一些类似的post: Swift 2parsingJSON – 不能 在Swift 3中下载 一个types为“AnyObject”的types的值和JSONparsing 。
但是,似乎在这里传达的想法并不适用于这种情况。
如何正确parsingSwift 3中的JSON响应? 在Swift 3中读取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调用结果的示例print(currentConditions)
["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
asynchronous方法。
“任何”没有下标成员
因为编译器不知道中间对象是什么types(例如currently
在["currently"]!["temperature"]
),并且由于您使用的是像NSDictionary
这样的基础集合types,所以编译器完全不知道types。
另外在Swift 3中,需要通知编译器所有下标对象的types。
您必须将JSON序列化的结果转换为实际的types。
此代码使用URLSession
和独有的 Swift本机types
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
:
许多(似乎所有)教程build议.mutableContainers
或.mutableLeaves
选项在Swift中完全是无稽之谈。 这两个选项是遗留的Objective-C选项将结果分配给NSMutable...
对象。 在Swift中,默认情况下,任何variables都是可变的,并且传递任何这些选项并将结果赋值给一个let
常量根本没有任何作用。 更进一步的,大部分的实现都不会改变反序列化的JSON。
在Swift中唯一(罕见的)选项是.allowFragments
,如果JSON根对象可以是一个值types( String
, Number
, Bool
或null
)而不是集合types( array
或dictionary
)之一,则这是必需的。 但通常省略options
参数,这意味着没有选项 。
================================================== =========================
parsingJSON的一些常规注意事项
JSON是一个排列良好的文本格式。 阅读JSONstring非常容易。 仔细阅读string 。 只有六种不同的types – 两个集合types和四个值types。
集合types是
- Array – JSON:方括号
[]
对象 – Swift:[Any]
但大多数情况下[[String:Any]]
- 字典 – JSON:花括号中的对象
{}
– Swift:[String:Any]
值types是
- string – JSON:双引号
"Foo"
,甚至"123"
或"false"
任何值 – Swift:String
- 数字 – JSON:数字值不用双引号
123
或123.0
– Swift:Int
或Double
- 布尔 – JSON:
true
或false
不用双引号 – Swift:true
或false
- null – JSON:
null
– Swift:NSNull
根据JSON规范,字典中的所有键都必须是String
。
基本上总是build议使用可选的绑定来安全地打开选项
如果根对象是字典( {}
),则将types转换为[String:Any]
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [String:Any] { ...
并通过键检索值( OneOfSupportedJSONTypes
是JSON集合或值types,如上所述)。
if let foo = parsedData["foo"] as? OneOfSupportedJSONTypes { print(foo) }
如果根对象是数组( []
),则将types转换为[[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) } }
在极less数情况下,JSON只是值types之一 – 而不是集合types – 您必须通过.allowFragments
选项并将结果转换为适当的值types
if let parsedData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? String { ...
苹果在Swift博客上发表了一篇全面的文章: 在Swift中使用JSON
Xcode 8 Beta 6在Swift 3上发生了一个很大的变化,那就是id现在导入为Any
而不是AnyObject
。
这意味着parsedData
作为最有可能的types[Any:Any]
的字典被返回。 没有使用debugging器,我无法确切地告诉你什么你的转换到NSDictionary
会做,但你看到的错误是因为dict!["currently"]!
有Any
types
那么,你如何解决这个问题呢? 从你引用它的方式,我假设dict!["currently"]!
是一本字典,所以你有很多select:
首先你可以做这样的事情:
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
希望这有帮助,恐怕我没有时间写一个示例应用程序来testing它。
最后一个注意事项:最简单的做法可能是在开始时将JSON负载简单地转换为[String: AnyObject]
。
let parsedData = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments) as! Dictionary<String, AnyObject>
let str = "{\"names\": [\"Bob\", \"Tim\", \"Tina\"]}" let data = str.data(using: String.Encoding.utf8, allowLossyConversion: false)! do { 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)") }
之后更新了isConnectToNetwork-Function,感谢这篇文章检查与Swift的互联网连接
我为它写了一个额外的方法:
import SystemConfiguration func loadingJSON(_ link:String, postString:String, completionHandler: @escaping (_ JSONObject: AnyObject) -> ()) { if(isConnectedToNetwork() == false){ completionHandler("-1" as AnyObject) return } let request = NSMutableURLRequest(url: URL(string: link)!) request.httpMethod = "POST" request.httpBody = postString.data(using: String.Encoding.utf8) let task = URLSession.shared.dataTask(with: request as URLRequest) { data, response, error in guard error == nil && data != nil else { // check for fundamental networking error print("error=\(error)") return } if let httpStatus = response as? HTTPURLResponse , httpStatus.statusCode != 200 { // check for http errors print("statusCode should be 200, but is \(httpStatus.statusCode)") print("response = \(response)") } //JSON successfull do { let parseJSON = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) DispatchQueue.main.async(execute: { completionHandler(parseJSON as AnyObject) }); } catch let error as NSError { print("Failed to load: \(error.localizedDescription)") } } task.resume() } func isConnectedToNetwork() -> Bool { var zeroAddress = sockaddr_in(sin_len: 0, sin_family: 0, sin_port: 0, sin_addr: in_addr(s_addr: 0), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0)) zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress)) zeroAddress.sin_family = sa_family_t(AF_INET) let defaultRouteReachability = withUnsafePointer(to: &zeroAddress) { $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {zeroSockAddress in SCNetworkReachabilityCreateWithAddress(nil, zeroSockAddress) } } var flags: SCNetworkReachabilityFlags = SCNetworkReachabilityFlags(rawValue: 0) if SCNetworkReachabilityGetFlags(defaultRouteReachability!, &flags) == false { return false } let isReachable = (flags.rawValue & UInt32(kSCNetworkFlagsReachable)) != 0 let needsConnection = (flags.rawValue & UInt32(kSCNetworkFlagsConnectionRequired)) != 0 let ret = (isReachable && !needsConnection) return ret }
所以,现在你可以在任何你想要的地方轻松地调用它
loadingJSON("yourDomain.com/login.php", postString:"email=\(userEmail!)&password=\(password!)") { parseJSON in if(String(describing: parseJSON) == "-1"){ print("No Internet") } else { if let loginSuccessfull = parseJSON["loginSuccessfull"] as? Bool { //... do stuff } }
我为此build立了quicktype 。 只需粘贴您的示例JSON,快速types为您的API数据生成此types的层次结构:
struct Forecast { let hourly: Hourly let daily: Daily let currently: Currently let flags: Flags let longitude: Double let latitude: Double let offset: Int let timezone: String } struct Hourly { let icon: String let data: [Currently] let summary: String } struct Daily { let icon: String let data: [Datum] let summary: String } struct Datum { let precipIntensityMax: Double let apparentTemperatureMinTime: Int let apparentTemperatureLowTime: Int let apparentTemperatureHighTime: Int let apparentTemperatureHigh: Double let apparentTemperatureLow: Double let apparentTemperatureMaxTime: Int let apparentTemperatureMax: Double let apparentTemperatureMin: Double let icon: String let dewPoint: Double let cloudCover: Double let humidity: Double let ozone: Double let moonPhase: Double let precipIntensity: Double let temperatureHigh: Double let pressure: Double let precipProbability: Double let precipIntensityMaxTime: Int let precipType: String? let sunriseTime: Int let summary: String let sunsetTime: Int let temperatureMax: Double let time: Int let temperatureLow: Double let temperatureHighTime: Int let temperatureLowTime: Int let temperatureMin: Double let temperatureMaxTime: Int let temperatureMinTime: Int let uvIndexTime: Int let windGust: Double let uvIndex: Int let windBearing: Int let windGustTime: Int let windSpeed: Double } struct Currently { let precipProbability: Double let humidity: Double let cloudCover: Double let apparentTemperature: Double let dewPoint: Double let ozone: Double let icon: String let precipIntensity: Double let temperature: Double let pressure: Double let precipType: String? let summary: String let uvIndex: Int let windGust: Double let time: Int let windBearing: Int let windSpeed: Double } struct Flags { let sources: [String] let isdStations: [String] let units: String }
它还生成无依赖关系的封送处理代码,将JSONSerialization.jsonObject
的返回值转换为Forecast
,其中包括一个便捷的构造函数,它使用JSONstring,以便快速parsing强types的Forecast
值并访问其字段:
let forecast = Forecast.from(json: jsonString)! print(forecast.daily.data[0].windGustTime)
你可以使用npm i -g quicktype
来安装快速types,使用npm i -g quicktype
或者使用networking界面来获得完整的代码,以便粘贴到你的游乐场。
问题在于API交互方法。JSONparsing仅在语法中更改。 主要的问题是提取数据的方式。 你正在使用的是获取数据的同步方式。 这在任何情况下都不起作用。 你应该使用的是一个asynchronous的方式来获取数据。 这样,您必须通过API请求数据,并等待数据响应。 您可以通过URL会话和Alamofire等第三方库来实现此目的。 以下是URL会话方法的代码。
let urlString = "https://api.forecast.io/forecast/apiKey/37.5673776,122.048951" let url = URL.init(string: urlString) URLSession.shared.dataTask(with:url!) { (data, response, error) in guard error == nil else { print(error) } do { let Data = try JSONSerialization.jsonObject(with: data!) as! [String:Any] // Note if your data is coming in Array you should be using [Any]() //Now your data is parsed in Data variable and you can use it normally let currentConditions = Data["currently"] as! [String:Any] print(currentConditions) let currentTemperatureF = currentConditions["temperature"] as! Double print(currentTemperatureF) } catch let error as NSError { print(error) } }.resume()