首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >正确解析Swift 3中的JSON

正确解析Swift 3中的JSON
EN

Stack Overflow用户
提问于 2016-09-10 14:43:15
回答 10查看 134.6K关注 0票数 123

我正在尝试获取一个JSON响应并将结果存储在一个变量中。在Xcode8的GM版本发布之前,我已经在Swift的以前版本中使用过这种代码。我在StackOverflow上看过一些类似的帖子:Swift 2 Parsing JSON - Cannot subscript a value of type 'AnyObject'JSON Parsing in Swift 3

然而,那里传达的思想似乎不适用于这种情况。

如何正确解析Swift 3中的JSON响应?在Swift 3中读取JSON的方式有什么变化吗?

下面是有问题的代码(它可以在操场上运行):

代码语言:javascript
复制
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)之后调用的结果示例

代码语言:javascript
复制
["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]
EN

回答 10

Stack Overflow用户

回答已采纳

发布于 2016-09-10 15:32:41

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

'Any‘没有下标成员

发生这种情况是因为编译器不知道中间对象是什么类型(例如["currently"]!["temperature"]中的currently ),而且由于您使用的是像NSDictionary这样的基础集合类型,所以编译器对这种类型一无所知。

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

您必须将序列化的结果强制转换为实际类型。

此代码仅使用 Swift本机类型的URLSession

代码语言:javascript
复制
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的所有键/值对,您可以这样写

代码语言:javascript
复制
 let currentConditions = parsedData["currently"] as! [String:Any]

  for (key, value) in currentConditions {
    print("\(key) - \(value) ")
  }

关于 jsonObject(with data**:**的说明

许多(似乎所有的)教程都建议使用.mutableContainers.mutableLeaves选项,这在Swift中完全是胡说八道。这两个选项是用于将结果分配给NSMutable...对象的遗留Objective-C选项。在Swift中,任何variable在默认情况下都是可变的,传递这些选项中的任何一个并将结果赋给let常量都没有任何效果。而且,大多数实现都不会改变反序列化的JSON。

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

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

解析JSON的一些一般注意事项

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

集合类型为

Swift数组- [String:Any]:方括号中的对象-

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

值类型为

Swift字符串- JSON:位于双引号"Foo"中的任何值,甚至是"123""false" -

  • String

  • Number - JSON: numeric not in双引号123123.0 - Swift:Intfalse

  • null - JSON:truefalse 不是用双引号括起来的E268>- Swift: D69123>或123.0- JSON:null - Swift:

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

基本上,总是建议使用可选绑定来安全地解包可选参数

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

代码语言:javascript
复制
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [String:Any] { ...

并使用键检索值(如上所述,OneOfSupportedJSONTypes可以是JSON集合,也可以是值类型。)

代码语言:javascript
复制
if let foo = parsedData["foo"] as? OneOfSupportedJSONTypes {
    print(foo)
} 

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

代码语言:javascript
复制
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]] { ...

遍历该数组

代码语言:javascript
复制
for item in parsedData {
    print(item)
}

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

代码语言:javascript
复制
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]], parsedData.count > 2,
   let item = parsedData[2] as? OneOfSupportedJSONTypes {
      print(item)
    }
}

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

代码语言:javascript
复制
if let parsedData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? String { ...

苹果公司在Swift博客上发表了一篇综合文章:Working with JSON in Swift

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

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

例如,问题中给定的JSON示例(稍作修改)

代码语言:javascript
复制
let jsonString = """
{"icon": "partly-cloudy-night", "precipProbability": 0, "pressure": 1015.39, "humidity": 0.75, "precip_intensity": 0, "wind_speed": 6.04, "summary": "Partly Cloudy", "ozone": 321.13, "temperature": 49.45, "dew_point": 41.75, "apparent_temperature": 47, "wind_bearing": 332, "cloud_cover": 0.28, "time": 1480846460}
"""

可以解码为结构Weather。Swift类型与上述相同。还有几个附加选项:

表示URL

  • 字符串可以直接解码为URL。可以使用.secondsSince1970.
  • snaked_cased .convertFromSnakeCase

  • time整数解码为Date可以使用dateDecodingStrategy JSON将JSON密钥转换为camelCase

代码语言:javascript
复制
struct Weather: Decodable {
    let icon, summary: String
    let pressure: Double, humidity, windSpeed : Double
    let ozone, temperature, dewPoint, cloudCover: Double
    let precipProbability, precipIntensity, apparentTemperature, windBearing : Int
    let time: Date
}

let data = Data(jsonString.utf8)
do {
    let decoder = JSONDecoder()
    decoder.dateDecodingStrategy = .secondsSince1970
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    let result = try decoder.decode(Weather.self, from: data)
    print(result)
} catch {
    print(error)
}

其他可编码源:

票数 174
EN

Stack Overflow用户

发布于 2016-09-10 15:34:27

在Swift 3的Xcode8Beta6中发生的一个很大的变化是,id现在作为Any而不是AnyObject导入。

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

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

首先,你可以这样做:

代码语言:javascript
复制
let currentConditionsDictionary: [String: AnyObject] = dict!["currently"]! as! [String: AnyObject]  

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

代码语言:javascript
复制
let currentTemperatureF = currentConditionsDictionary["temperature"] as! Double

或者,如果你愿意,你可以排成一排:

代码语言:javascript
复制
let currentTemperatureF = (dict!["currently"]! as! [String: AnyObject])["temperature"]! as! Double

希望这能有所帮助,恐怕我还没有时间写一个示例应用程序来测试它。

最后一点:最简单的做法可能是在一开始就将JSON有效负载转换为[String: AnyObject]

代码语言:javascript
复制
let parsedData = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments) as! Dictionary<String, AnyObject>
票数 12
EN

Stack Overflow用户

发布于 2017-01-24 13:19:51

代码语言:javascript
复制
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)")
}
票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/39423367

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档