在 Swift 3 中正确解析 JSON [英] Correctly Parsing JSON in Swift 3

查看:31
本文介绍了在 Swift 3 中正确解析 JSON的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm trying to fetch a JSON response and store the results in a variable. I've had versions of this code work in previous releases of Swift, until the GM version of Xcode 8 was released. I had a look at a few similar posts on StackOverflow: Swift 2 Parsing JSON - Cannot subscript a value of type 'AnyObject' and JSON Parsing in Swift 3.

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

However, it seems the ideas conveyed there do not apply in this scenario.

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

How do I correctly parse the JSON reponse in Swift 3? Has something changed in the way JSON is read in Swift 3?

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

Below is the code in question (it can be run in a playground):

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:
 (error)")
        }
    }
}

以下是 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 这样的异步方法.

First of all never load data synchronously from a remote URL, use always asynchronous methods like URLSession.

'Any' 没有下标成员

'Any' has no subscript members

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

occurs because the compiler has no idea of what type the intermediate objects are (for example currently in ["currently"]!["temperature"]) and since you are using Foundation collection types like NSDictionary the compiler has no idea at all about the type.

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

Additionally in Swift 3 it's required to inform the compiler about the type of all subscripted objects.

您必须将 JSON 序列化的结果转换为实际类型.

此代码使用URLSession独占 Swift 原生类型

This code uses URLSession and exclusively Swift native 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 的所有键/值对,您可以编写

To print all key / value pairs of currentConditions you could write

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

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

关于jsonObject(with data)的说明:

A note regarding jsonObject(with data:

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

Many (it seems all) tutorials suggest .mutableContainers or .mutableLeaves options which is completely nonsense in Swift. The two options are legacy Objective-C options to assign the result to NSMutable... objects. In Swift any variable is mutable by default and passing any of those options and assigning the result to a let constant has no effect at all. Further most of the implementations are never mutating the deserialized JSON anyway.

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

The only (rare) option which is useful in Swift is .allowFragments which is required if if the JSON root object could be a value type(String, Number, Bool or null) rather than one of the collection types (array or dictionary). But normally omit the options parameter which means No options.

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

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

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

JSON is a well-arranged text format. It's very easy to read a JSON string. Read the string carefully. There are only six different types – two collection types and four value types.

集合类型是

  • Array - JSON:方括号中的对象 [] - Swift:[Any] 但在大多数情况下 [[String]:Any]]
  • 字典 - JSON:花括号中的对象 {} - Swift:[String:Any]
  • Array - JSON: objects in square brackets [] - Swift: [Any] but in most cases [[String:Any]]
  • Dictionary - JSON: objects in curly braces {} - Swift: [String:Any]

值类型是

  • String - JSON:双引号 "Foo" 中的任何值,甚至 "123""false" – Swift:String
  • 数字 - JSON:数值双引号 123123.0 - Swift:IntDouble
  • Bool - 双引号中的 JSON:truefalse not - Swift:truefalse
  • null - JSON:null - Swift:NSNull
  • String - JSON: any value in double quotes "Foo", even "123"or "false" – Swift: String
  • Number - JSON: numeric values not in double quotes 123 or 123.0 – Swift: Int or Double
  • Bool - JSON: true or false not in double quotes – Swift: true or false
  • null - JSON: null – Swift: NSNull

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

According to the JSON specification all keys in dictionaries are required to be String.

基本上总是推荐使用可选绑定来安全地解包可选

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

If the root object is a dictionary ({}) cast the type to [String:Any]

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

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

and retrieve values by keys with (OneOfSupportedJSONTypes is either JSON collection or value type as described above.)

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 you need an item at specific index check also if the index exists

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

<小时>

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


In the rare case that the JSON is simply one of the value types – rather than a collection type – you have to pass the .allowFragments option and cast the result to the appropriate value type for example

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

Apple 在 Swift 博客中发表了一篇综合文章:在 Swift 中使用 JSON

Apple has published a comprehensive article in the Swift Blog: Working with JSON in Swift

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

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

例如问题中给定的 JSON 示例(略有修改)

For example the given JSON sample in the question (slightly modified)

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 类型与上述相同.还有一些额外的选项:

can be decoded into the struct Weather. The Swift types are the same as described above. There are a few additional options:

  • 表示URL 的字符串可以直接解码为URL.
  • time 整数可以用 dateDecodingStrategy .secondsSince1970 解码为 Date.
  • snaked_cased JSON 密钥可以通过 keyDecodingStrategy .convertFromSnakeCase
  • 转换为 camelCase
  • Strings representing an URL can be decoded directly as URL.
  • The time integer can be decoded as Date with the dateDecodingStrategy .secondsSince1970.
  • snaked_cased JSON keys can be converted to camelCase with the keyDecodingStrategy .convertFromSnakeCase
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)
}

其他编码来源:

这篇关于在 Swift 3 中正确解析 JSON的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆