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 var
iable 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
、Number
、Bool
或null
),而不是集合类型(array
或dictionary
)之一,则需要.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双引号中的数字值
123
或123.0
-Swift:Int
或Double
- Bool-JSON:
true
或false
双引号中的not-Swift:true
或false
- 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)
}
其他可编码来源: