如何在Swift 3中正确解析JSON?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (48)

我试图获取JSON响应并将结果存储在一个变量中。我已经在以前版本的Swift中使用过这些代码的版本,直到Xcode 8的GM版本发布。

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

如何正确解析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)")
        }
    }
}

以下是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

'任何'没有下标成员

是因为编译器没有的中间对象是什么类型(例如理念currently["currently"]!["temperature"]),并且由于使用的是基金会收藏类型,如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()

要打印所有的键/值对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,默认情况下任何可变参数都是可变的,并且传递任何这些选项并将结果赋值给let常量根本没有任何作用。更进一步的,大多数的实现都不会改变反序列化的JSON。

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

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

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

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

集合类型是

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

值类型是

  • 字符串 - JSON:双引号中的任何值"Foo",偶数"123""false"- Swift:String
  • 数字 - 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] { ...

并通过键检索值(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博客上发表了一篇全面的文章:在Swift中使用JSON

用户回答回答于

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]  

这会给你一个字典对象,你可以查询值,所以你可以像这样得到你的温度:

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>

扫码关注云+社区