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.

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

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

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

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)")
        }
    }
}

Edit: Here is a sample of the results from the API call after 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]

推荐答案

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

'Any' has no subscript members

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中,需要通知编译器all个订阅对象的类型.

You have to cast the result of the JSON serialization to the actual type.

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个键/值对,您可以这样写

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

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

A note regarding jsonObject(with data:

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根对象可以是值类型(StringNumberBoolnull),而不是集合类型(arraydictionary)之一,则需要.allowFragments.但通常忽略options参数,即No options.

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

Some general considerations to parse JSON

JSON是一种排列良好的文本格式.读取JSON字符串非常容易.Read the string carefully.只有六种不同的类型——两种收集类型和四种值类型.


The collection types are

  • Array-JSON:方括号中的对象[]-Swift:[Any]但在大多数情况下是[[String:Any]]
  • Dictionary - JSON: objects in curly braces {} - Swift: [String:Any]

The value types are

  • String-JSON:双引号中的任意值"Foo",甚至"123""false"–Swift:String
  • Number-JSON:not双引号中的数字值123123.0-Swift:IntDouble
  • Bool-JSON:truefalse双引号中的not-Swift:truefalse
  • null - JSON: null – Swift: NSNull

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


Basically it's always recommeded to use optional bindings to unwrap optionals safely

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

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

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)
} 

If the root object is an array ([]) cast the type to [[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)
    }
}

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 { ...

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


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

In Swift 4+ the Codable protocol provides a more convenient way to parse JSON directly into structs / classes.

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}
"""

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

  • 表示URL的字符串可以直接解码为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)
}

其他可编码来源:

Json相关问答推荐

服务器不返回JSON

从Json响应中为需要每个值的Post请求提取多个值

Vegalite中分组条形图中的偏移量问题

如何创建生成两个不同对象的JSON数组的SQL查询?

将PNG图像保存为Python中的JSON文件

419(未知状态)使用laravel处理PUT请求

使用 jq 从带有转义反斜杠字符的嵌套 JSON 中提取数据

Oracle JSON 查询中的动态列列表

将=分隔值文件转换为:json文件

Kotlin Android Room 处理 Moshi TypeConverter 中的空对象列表

jq:用列表包装所有第一级原子值

如何找出实际安装了哪个版本的 bower 包?

Java的JSON字符串整洁/格式化程序

了解 JSON Schema 草稿版本 4 中的additionalProperties关键字

以 unicode 将 pandas DataFrame 写入 JSON

如何使用 LWP 发出 JSON POST 请求?

如何使用 Jackson 注释从 HttpResponse 反序列化 JSON 对象?

在 Android 中使用带有 post 参数的 HttpClient 和 HttpPost

[__NSCFNumber 长度]:发送到实例 UITableView 的无法识别的 Select 器

添加json文件注释