在Swift 3中正确解析JSON [英] Correctly Parsing JSON in Swift 3
问题描述
我正在尝试获取JSON响应并将结果存储在变量中.在以前的Swift版本中,我已经使用过该代码的版本,直到Xcode 8的GM版本发布为止.我在StackOverflow上看到了一些类似的帖子:在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:\n \(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' has no subscript members
出现
的原因是,编译器不知道中间对象是什么类型(例如["currently"]!["temperature"]
中的currently
),并且由于使用的是像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中,默认情况下任何var
iable都是可变的,并且传递任何这些选项并将结果分配给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 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
),则必须使用.allowFragments
而不是一种收集类型(array
或dictionary
).但通常会省略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.
集合类型为
- 数组-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
- Number -JSON:双引号
123
或123.0
中的数字值非 – Swift:Int
或Double
- Bool -JSON:
true
或false
不双引号– Swift:true
或false
- 空-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
or123.0
– Swift:Int
orDouble
- Bool - JSON:
true
orfalse
not in double quotes – Swift:true
orfalse
- 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 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)
}
}
在极少数情况下,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
. - 可以使用
dateDecodingStrategy
.secondsSince1970
将time
整数解码为Date
.
可以使用 - snaked_cased JSON密钥转换为 camelCase
keyDecodingStrategy
.convertFromSnakeCase
将- Strings representing an
URL
can be decoded directly asURL
. - The
time
integer can be decoded asDate
with thedateDecodingStrategy
.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)
}
其他可编码来源:
- Apple: Encoding and Decoding Custom Types
- HackingWithSwift: Codable Cheat Sheet
- Ray Wenderlich: Encoding and Decoding in Swift
这篇关于在Swift 3中正确解析JSON的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!