在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同步加载数据 ,总是使用像URLSessionasynchronous方法。

“任何”没有下标成员

因为编译器不知道中间对象是什么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( StringNumberBoolnull )而不是集合types( arraydictionary )之一,则这是必需的。 但通常省略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:数字值不用双引号123123.0 – Swift: IntDouble
  • 布尔 – JSON: truefalse 不用双引号 – Swift: truefalse
  • 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"]!Anytypes

那么,你如何解决这个问题呢? 从你引用它的方式,我假设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()