且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

在 Swift 3 中正确解析 JSON

更新时间:2022-11-19 08:04:45

首先永远不要从远程 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)
}

其他编码来源: