正确解析Swift 3中的JSON

use*_*039 118 parsing json swift swift3 xcode8

我正在尝试获取JSON响应并将结果存储在变量中.我已经在Swift的早期版本中使用了此代码的版本,直到GM版本的Xcode 8发布.我看了一下StackOverflow上的一些类似的帖子:Swift 2解析JSON - 无法下标Swift 3中的'AnyObject'JSON解析类型的值.

但是,似乎那里传达的想法并不适用于这种情况.

如何在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)")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑:以下是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]
Run Code Online (Sandbox Code Playgroud)

vad*_*ian 167

首先,永远不要从远程URL同步加载数据,总是使用异步方法URLSession.

'任何'没有下标成员

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

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

您必须将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()
Run Code Online (Sandbox Code Playgroud)

要打印currentConditions您可以写的所有键/值对

 let currentConditions = parsedData["currently"] as! [String:Any]

  for (key, value) in currentConditions {
    print("\(key) - \(value) ")
  }
Run Code Online (Sandbox Code Playgroud)

关于jsonObject(with data:

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

唯一的(罕见)选项,在夫特是有用是.allowFragments如果如果JSON根对象可以是一个值类型是必需(String,Number,Boolnull)而不是集合类型中的一个(arraydictionary).但通常省略options参数,这意味着没有选项.

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

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

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


集合类型是

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

值类型是

  • String - JSON:双引号中的任何值"Foo",偶数"123""false"- Swift:String
  • Number - JSON:不是双引号的数值123123.0- Swift:IntDouble
  • Bool - JSON:truefalse 不用双引号 - Swift:truefalse
  • null - JSON:null- Swift:NSNull

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


基本上,它始终建议使用可选绑定来安全地打开选项

如果根对象是字典({}),则将类型转换为[String:Any]

if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [String:Any] { ...
Run Code Online (Sandbox Code Playgroud)

并通过键检索值(OneOfSupportedJSONTypes如上所述是JSON集合或值类型.)

if let foo = parsedData["foo"] as? OneOfSupportedJSONTypes {
    print(foo)
} 
Run Code Online (Sandbox Code Playgroud)

如果根对象是一个数组([]),则将类型转换为[[String:Any]]

if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]] { ...
Run Code Online (Sandbox Code Playgroud)

并使用迭代遍历数组

for item in parsedData {
    print(item)
}
Run Code Online (Sandbox Code Playgroud)

如果您需要特定索引处的项目,请检查索引是否存在

if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]], parsedData.count > 2,
   let item = parsedData[2] as? OneOfSupportedJSONTypes {
      print(item)
    }
}
Run Code Online (Sandbox Code Playgroud)

在极少数情况下,JSON只是值类型之一 - 而不是集合类型 - 您必须传递.allowFragments选项并将结果转换为适当的值类型,例如

if let parsedData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? String { ...
Run Code Online (Sandbox Code Playgroud)

Apple在Swift博客中发表了一篇全面的文章:在Swift中使用JSON

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


dis*_*ilo 12

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

这意味着它将parsedData作为最有可能与该类型的字典返回[Any:Any].如果不使用调试器,我无法确切地告诉你你的演员NSDictionary会做什么,但你看到的错误是因为dict!["currently"]!有类型Any

那么,你是如何解决这个问题的?从你引用它的方式来看,我假设dict!["currently"]!是一本字典,所以你有很多选择:

首先你可以这样做:

let currentConditionsDictionary: [String: AnyObject] = dict!["currently"]! as! [String: AnyObject]  
Run Code Online (Sandbox Code Playgroud)

这将为您提供一个字典对象,然后您可以查询值,这样您就可以得到这样的温度:

let currentTemperatureF = currentConditionsDictionary["temperature"] as! Double
Run Code Online (Sandbox Code Playgroud)

或者,如果您愿意,可以在线执行:

let currentTemperatureF = (dict!["currently"]! as! [String: AnyObject])["temperature"]! as! Double
Run Code Online (Sandbox Code Playgroud)

希望这有帮助,我担心我没有时间编写示例应用程序来测试它.

最后一点需要注意的是:最简单的方法可能是在开始时简单地将JSON有效负载转换为[String: AnyObject]正确的.

let parsedData = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments) as! Dictionary<String, AnyObject>
Run Code Online (Sandbox Code Playgroud)


小智 6

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)")
}
Run Code Online (Sandbox Code Playgroud)


Mar*_*ber 5

后来更新了isConnectToNetwork-Function,感谢这篇文章

我为其编写了一个额外的方法:

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
}
Run Code Online (Sandbox Code Playgroud)

所以现在您可以在应用程序中随时随地轻松调用它

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
    }
}
Run Code Online (Sandbox Code Playgroud)


Dav*_*gel 5

我正是为此目的构建了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
}
Run Code Online (Sandbox Code Playgroud)

它还生成无依赖的封送处理代码以将 的返回值哄骗JSONSerialization.jsonObject到 a 中Forecast,包括一个接受 JSON 字符串的便捷构造函数,以便您可以快速解析强类型Forecast值并访问其字段:

let forecast = Forecast.from(json: jsonString)!
print(forecast.daily.data[0].windGustTime)
Run Code Online (Sandbox Code Playgroud)

你可以从 npm 安装 quicktypenpm i -g quicktype或者使用 web UI来获取完整的生成代码以粘贴到你的操场上。