首页 文章

正确解析Swift 3中的JSON

提问于
浏览
104

我已经在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的方式有什么变化吗?

以下是有问题的代码(可以在游乐场中运行):

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)")
        }
    }
}

Edit: 以下是 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]

6 回答

  • 3

    首先 never load data synchronously from a remote URL ,始终使用异步方法,如 URLSession .

    '任何'没有下标成员

    之所以发生是因为编译器不知道中间对象是什么类型的(例如 currently["currently"]!["temperature"] 中),并且由于您使用的是基础集合类型,如 NSDictionary ,编译器根本不知道该类型 .

    此外,在Swift 3中,需要通知编译器 all 下标对象的类型 .

    You have to cast the result of the JSON serialization to the actual type.

    此代码使用 URLSessionexclusively 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) ")
      }
    

    A note regarding jsonObject(with data:

    许多(似乎都是)教程建议 .mutableContainers.mutableLeaves 选项在Swift中完全是无意义的 . 这两个选项是遗留的Objective-C选项,用于将结果分配给 NSMutable... 对象 . 在Swift中,默认情况下 var iable是可变的,并且传递任何这些选项并将结果赋值给 let 常量根本没有效果 . 此外,大多数实现都不会改变反序列化的JSON .

    在Swift中唯一有用的(罕见)选项是 .allowFragments ,如果JSON根对象可以是值类型( StringNumberBoolnull )而不是其中一种集合类型( arraydictionary ),则需要该选项 . 但通常省略 options 参数,这意味着没有选项 .

    ================================================== =========================

    解析JSON的一些常规注意事项

    JSON是一种排列良好的文本格式 . 读取JSON字符串非常容易 . Read the string carefully . 只有六种不同的类型 - 两种集合类型和四种值类型 .


    集合类型是

    • 数组 - JSON:方括号中的对象 [] - Swift: [Any] 但在大多数情况下 [[String:Any]]

    • Dictionary - JSON:花括号中的对象 {} - Swift: [String:Any]

    值类型是

    • String - JSON:双引号中的任何值 "Foo" ,偶数 "123""false" - Swift: String

    • Number - JSON:数字值 not 用双引号 123123.0 - Swift: IntDouble

    • Bool - JSON: truefalse not 双引号 - Swift: truefalse

    • null - JSON: null - Swift: NSNull

    根据JSON规范,字典中的所有键都必须是 String .


    Basically it's always recommeded to use optional bindings to unwrap optionals safely

    如果根对象是字典( {} ),则将类型强制转换为 [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

    Update :在Swift 4中, Codable 协议提供了一种更方便的方法,可以将JSON直接解析为结构/类 .

  • 4

    Xcode 8 Beta 6为Swift 3发生的一个重大变化是id现在导入为 Any 而不是 AnyObject .

    这意味着 parsedData 作为字典返回,类型为 [Any:Any] . 在没有使用调试器的情况下,我无法准确地告诉您对 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>
    
  • 153
    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)")
    }
    
  • 12

    我为此目的 Build 了quicktype . 只需粘贴您的示例JSON,quicktype就会为您的API数据生成此类型层次结构:

    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 ,包括带有JSON字符串的便捷构造函数,以便您可以快速解析强类型 Forecast 值并访问其字段:

    let forecast = Forecast.from(json: jsonString)!
    print(forecast.daily.data[0].windGustTime)
    

    您可以使用 npm i -g quicktypeuse the web UI从npm安装quicktype,以将完整生成的代码粘贴到您的游乐场 .

  • 0

    Updated 之后是isConnectToNetwork-Function,感谢这篇文章Check for internet connection with 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
                    }
      }
    
  • 6

    问题在于API交互方法 . 仅在语法中更改JSON解析 . 主要问题在于获取数据的方式 . 您使用的是获取数据的同步方式 . 这并不适用于所有情况 . 您应该使用的是获取数据的异步方法 . 通过这种方式,您必须通过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()
    

相关问题