正确解析 Swift 中的 JSON 3
Correctly Parsing JSON in Swift 3
我正在尝试获取 JSON 响应并将结果存储在一个变量中。在 Xcode 8 的 GM 版本发布之前,我已经在之前的 Swift 版本中使用了此代码的版本。我在 Whosebug 上查看了一些类似的帖子: and 。
但是,这里传达的想法似乎不适用于这种情况。
如何正确解析 Swift 3 中的 JSON 响应?
在 Swift 3 中读取 JSON 的方式有什么变化吗?
下面是有问题的代码(在 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)
之后 API 调用的结果示例
["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
.
'Any' has no subscript members
发生是因为编译器不知道中间对象是什么类型(例如 ["currently"]!["temperature"]
中的 currently
)并且因为您使用的是 NSDictionary
编译器之类的基础集合类型完全不知道类型。
另外在Swift 3中需要通知编译器关于所有下标对象的类型。
您必须将 JSON 序列化的结果转换为实际类型。
此代码仅使用 URLSession
和 Swift 本机类型
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) ")
}
关于jsonObject(with data
的注释:
许多(似乎全部)教程建议 .mutableContainers
或 .mutableLeaves
选项,这在 Swift 中完全是胡说八道。这两个选项是遗留 Objective-C 选项,用于将结果分配给 NSMutable...
对象。在 Swift 中,任何 var
iable 默认情况下都是可变的,传递任何这些选项并将结果分配给 let
常量根本没有效果。此外,大多数实现永远不会改变反序列化的 JSON。
在 Swift 中唯一有用的(罕见)选项是 .allowFragments
如果 JSON 根对象可以是值类型(String
, Number
, Bool
或 null
) 而不是其中一种集合类型 (array
或 dictionary
)。但通常会省略 options
参数,这意味着 没有选项 。
=========================================== ==============================
解析的一些一般注意事项JSON
JSON是一种排列整齐的文本格式。读取 JSON 字符串非常容易。 仔细阅读字符串。只有六种不同的类型 – 两种集合类型和四种值类型。
集合类型是
- 数组-JSON:方括号中的对象
[]
-Swift:[Any]
但在大多数情况下[[String:Any]]
- 字典 - JSON: 花括号中的对象
{}
- Swift: [String:Any]
值类型是
- String - JSON: 双引号中的任意值
"Foo"
, 甚至 "123"
or "false"
– Swift: String
- 数字 - JSON:数值不是双引号
123
或123.0
– Swift: Int
或 Double
- Bool - JSON:
true
or false
not in double quotes – Swift: true
或 false
- null - JSON:
null
– Swift: NSNull
根据 JSON 规范,字典中的所有键都必须是 String
。
基本上总是推荐使用可选绑定来安全地解包可选
如果根对象是字典 ({}
) 将类型转换为 [String:Any]
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [String:Any] { ...
并通过键检索值(OneOfSupportedJSONTypes
是 JSON 集合或值类型,如上所述。)
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 let parsedData = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]], parsedData.count > 2,
let item = parsedData[2] as? OneOfSupportedJSONTypes {
print(item)
}
}
在极少数情况下,JSON 只是其中一种值类型,而不是集合类型,您必须传递 .allowFragments
选项并将结果转换为适当的值类型例如
if let parsedData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? String { ...
Apple 在 Swift 博客中发表了一篇综合文章:Working with JSON in Swift
=========================================== ==============================
在Swift 4+中,Codable
协议提供了一种更方便的方法来将JSON直接解析为结构/类.
例如问题中给定的 JSON 样本(稍作修改)
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 类型与上述相同。还有一些其他选项:
- 表示
URL
的字符串可以直接解码为URL
。
-
time
整数可以用 dateDecodingStrategy
.secondsSince1970
. 解码为 Date
- snaked_cased JSON 键可以转换为 camelCase 和
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)
}
其他 Codable 来源:
Xcode 8 Beta 6 for Swift 3 发生的一个重大变化是 id 现在导入为 Any
而不是 AnyObject
。
这意味着 parsedData
作为最有可能类型为 [Any:Any]
的字典返回。如果不使用调试器,我无法确切地告诉你你对 NSDictionary
的转换会做什么,但你看到的错误是因为 dict!["currently"]!
的类型为 Any
那么,你是如何解决这个问题的?从你引用它的方式来看,我假设 dict!["currently"]!
是一本字典,所以你有很多选择:
首先你可以这样做:
let currentConditionsDictionary: [String: AnyObject] = dict!["currently"]! as! [String: AnyObject]
这会给你一个字典对象,然后你可以查询值,这样你就可以像这样得到你的温度:
let currentTemperatureF = currentConditionsDictionary["temperature"] as! Double
或者,如果您愿意,可以排队:
let currentTemperatureF = (dict!["currently"]! as! [String: AnyObject])["temperature"]! as! Double
希望这有帮助,恐怕我还没有时间编写示例应用程序来测试它。
最后一点:最简单的方法可能是在开始时将 JSON 有效负载简单地转换为 [String: AnyObject]
。
let parsedData = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments) as! Dictionary<String, AnyObject>
更新之后的isConnectToNetwork-Function
,多亏了这个。
我为它写了一个额外的方法:
import SystemConfiguration
func loadingJSON(_ link:String, postString:String, completionHandler: @escaping (_ JSONObject: AnyObject) -> ()) {
if(isConnectedToNetwork() == false){
completionHandler("-1" as AnyObject)
return
}
let request = NSMutableURLRequest(url: URL(string: link)!)
request.httpMethod = "POST"
request.httpBody = postString.data(using: String.Encoding.utf8)
let task = URLSession.shared.dataTask(with: request as URLRequest) { data, response, error in
guard error == nil && data != nil else { // check for fundamental networking error
print("error=\(error)")
return
}
if let httpStatus = response as? HTTPURLResponse , httpStatus.statusCode != 200 { // check for http errors
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(response)")
}
//JSON successfull
do {
let parseJSON = try JSONSerialization.jsonObject(with: data!, options: .allowFragments)
DispatchQueue.main.async(execute: {
completionHandler(parseJSON as AnyObject)
});
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
}
task.resume()
}
func isConnectedToNetwork() -> Bool {
var zeroAddress = sockaddr_in(sin_len: 0, sin_family: 0, sin_port: 0, sin_addr: in_addr(s_addr: 0), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress))
zeroAddress.sin_family = sa_family_t(AF_INET)
let defaultRouteReachability = withUnsafePointer(to: &zeroAddress) {
[=10=].withMemoryRebound(to: sockaddr.self, capacity: 1) {zeroSockAddress in
SCNetworkReachabilityCreateWithAddress(nil, zeroSockAddress)
}
}
var flags: SCNetworkReachabilityFlags = SCNetworkReachabilityFlags(rawValue: 0)
if SCNetworkReachabilityGetFlags(defaultRouteReachability!, &flags) == false {
return false
}
let isReachable = (flags.rawValue & UInt32(kSCNetworkFlagsReachable)) != 0
let needsConnection = (flags.rawValue & UInt32(kSCNetworkFlagsConnectionRequired)) != 0
let ret = (isReachable && !needsConnection)
return ret
}
现在您可以在您的应用程序中随时随地轻松调用它
loadingJSON("yourDomain.com/login.php", postString:"email=\(userEmail!)&password=\(password!)") { parseJSON in
if(String(describing: parseJSON) == "-1"){
print("No Internet")
} else {
if let loginSuccessfull = parseJSON["loginSuccessfull"] as? Bool {
//... do stuff
}
}
let str = "{\"names\": [\"Bob\", \"Tim\", \"Tina\"]}"
let data = str.data(using: String.Encoding.utf8, allowLossyConversion: false)!
do {
let json = try JSONSerialization.jsonObject(with: data, options: []) as! [String: AnyObject]
if let names = json["names"] as? [String]
{
print(names)
}
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
我正是为此而构建了 quicktype。只需粘贴您的示例 JSON,quicktype 就会为您的 API 数据生成此类型层次结构:
struct Forecast {
let hourly: Hourly
let daily: Daily
let currently: Currently
let flags: Flags
let longitude: Double
let latitude: Double
let offset: Int
let timezone: String
}
struct Hourly {
let icon: String
let data: [Currently]
let summary: String
}
struct Daily {
let icon: String
let data: [Datum]
let summary: String
}
struct Datum {
let precipIntensityMax: Double
let apparentTemperatureMinTime: Int
let apparentTemperatureLowTime: Int
let apparentTemperatureHighTime: Int
let apparentTemperatureHigh: Double
let apparentTemperatureLow: Double
let apparentTemperatureMaxTime: Int
let apparentTemperatureMax: Double
let apparentTemperatureMin: Double
let icon: String
let dewPoint: Double
let cloudCover: Double
let humidity: Double
let ozone: Double
let moonPhase: Double
let precipIntensity: Double
let temperatureHigh: Double
let pressure: Double
let precipProbability: Double
let precipIntensityMaxTime: Int
let precipType: String?
let sunriseTime: Int
let summary: String
let sunsetTime: Int
let temperatureMax: Double
let time: Int
let temperatureLow: Double
let temperatureHighTime: Int
let temperatureLowTime: Int
let temperatureMin: Double
let temperatureMaxTime: Int
let temperatureMinTime: Int
let uvIndexTime: Int
let windGust: Double
let uvIndex: Int
let windBearing: Int
let windGustTime: Int
let windSpeed: Double
}
struct Currently {
let precipProbability: Double
let humidity: Double
let cloudCover: Double
let apparentTemperature: Double
let dewPoint: Double
let ozone: Double
let icon: String
let precipIntensity: Double
let temperature: Double
let pressure: Double
let precipType: String?
let summary: String
let uvIndex: Int
let windGust: Double
let time: Int
let windBearing: Int
let windSpeed: Double
}
struct Flags {
let sources: [String]
let isdStations: [String]
let units: String
}
它还生成无依赖编组代码,以将 JSONSerialization.jsonObject
的 return 值哄骗到 Forecast
,包括一个方便的构造函数,它接受 JSON 字符串,因此您可以快速解析强类型 Forecast
值并访问其字段:
let forecast = Forecast.from(json: jsonString)!
print(forecast.daily.data[0].windGustTime)
您可以使用 npm i -g quicktype
或 use the web UI 从 npm 安装 quicktype 以将完整的生成代码粘贴到您的 playground 中。
问题出在 API 交互方法上。 JSON 解析仅在语法上发生了变化。主要问题在于获取数据的方式。您使用的是同步获取数据的方式。这并非在所有情况下都有效。您应该使用的是一种异步方式来获取数据。这样就得通过API请求数据,然后等待它响应数据。您可以使用 URL 会话和 Alamofire
等第三方库来实现此目的。下面是 URL 会话方法的代码。
let urlString = "https://api.forecast.io/forecast/apiKey/37.5673776,122.048951"
let url = URL.init(string: urlString)
URLSession.shared.dataTask(with:url!) { (data, response, error) in
guard error == nil else {
print(error)
}
do {
let Data = try JSONSerialization.jsonObject(with: data!) as! [String:Any]
// Note if your data is coming in Array you should be using [Any]()
//Now your data is parsed in Data variable and you can use it normally
let currentConditions = Data["currently"] as! [String:Any]
print(currentConditions)
let currentTemperatureF = currentConditions["temperature"] as! Double
print(currentTemperatureF)
} catch let error as NSError {
print(error)
}
}.resume()
Swift 具有强大的类型推断。让我们摆脱“if let”或“guard let”样板文件并使用函数式方法强制解包:
- 这是我们的JSON。我们可以使用可选的 JSON 或通常的。我在我们的例子中使用可选的:
let json: Dictionary<String, Any>? = ["current": ["temperature": 10]]
- 辅助函数。我们只需要编写一次,然后在任何字典中重复使用:
/// Curry
public func curry<A, B, C>(_ f: @escaping (A, B) -> C) -> (A) -> (B) -> C {
return { a in
{ f(a, [=11=]) }
}
}
/// Function that takes key and optional dictionary and returns optional value
public func extract<Key, Value>(_ key: Key, _ json: Dictionary<Key, Any>?) -> Value? {
return json.flatMap {
cast([=11=][key])
}
}
/// Function that takes key and return function that takes optional dictionary and returns optional value
public func extract<Key, Value>(_ key: Key) -> (Dictionary<Key, Any>?) -> Value? {
return curry(extract)(key)
}
/// Precedence group for our operator
precedencegroup RightApplyPrecedence {
associativity: right
higherThan: AssignmentPrecedence
lowerThan: TernaryPrecedence
}
/// Apply. g § f § a === g(f(a))
infix operator § : RightApplyPrecedence
public func §<A, B>(_ f: (A) -> B, _ a: A) -> B {
return f(a)
}
/// Wrapper around operator "as".
public func cast<A, B>(_ a: A) -> B? {
return a as? B
}
- 这是我们的魔法 - 提取值:
let temperature = (extract("temperature") § extract("current") § json) ?? NSNotFound
只需一行代码,无需强制解包或手动类型转换。此代码在 playground 中有效,因此您可以复制并检查它。这是一个实现 on GitHub.
这是解决您问题的另一种方法。因此,请查看以下解决方案。希望对你有帮助。
let str = "{\"names\": [\"Bob\", \"Tim\", \"Tina\"]}"
let data = str.data(using: String.Encoding.utf8, allowLossyConversion: false)!
do {
let json = try JSONSerialization.jsonObject(with: data, options: []) as! [String: AnyObject]
if let names = json["names"] as? [String] {
print(names)
}
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
{
"User":[
{
"FirstUser":{
"name":"John"
},
"Information":"XY",
"SecondUser":{
"name":"Tom"
}
}
]
}
如果我使用以前的 json 创建模型
使用此 link [博客]:http://www.jsoncafe.com 生成 Codable 结构或任何格式
型号
import Foundation
struct RootClass : Codable {
let user : [Users]?
enum CodingKeys: String, CodingKey {
case user = "User"
}
init(from decoder: Decoder) throws {
let values = try? decoder.container(keyedBy: CodingKeys.self)
user = try? values?.decodeIfPresent([Users].self, forKey: .user)
}
}
struct Users : Codable {
let firstUser : FirstUser?
let information : String?
let secondUser : SecondUser?
enum CodingKeys: String, CodingKey {
case firstUser = "FirstUser"
case information = "Information"
case secondUser = "SecondUser"
}
init(from decoder: Decoder) throws {
let values = try? decoder.container(keyedBy: CodingKeys.self)
firstUser = try? FirstUser(from: decoder)
information = try? values?.decodeIfPresent(String.self, forKey: .information)
secondUser = try? SecondUser(from: decoder)
}
}
struct SecondUser : Codable {
let name : String?
enum CodingKeys: String, CodingKey {
case name = "name"
}
init(from decoder: Decoder) throws {
let values = try? decoder.container(keyedBy: CodingKeys.self)
name = try? values?.decodeIfPresent(String.self, forKey: .name)
}
}
struct FirstUser : Codable {
let name : String?
enum CodingKeys: String, CodingKey {
case name = "name"
}
init(from decoder: Decoder) throws {
let values = try? decoder.container(keyedBy: CodingKeys.self)
name = try? values?.decodeIfPresent(String.self, forKey: .name)
}
}
解析
do {
let res = try JSONDecoder().decode(RootClass.self, from: data)
print(res?.user?.first?.firstUser?.name ?? "Yours optional value")
} catch {
print(error)
}
Swift 5
无法从您的 api 获取数据。
解析 json 的最简单方法是使用 Decodable
协议。或者 Codable
(Encodable & Decodable
)。
例如:
let json = """
{
"dueDate": {
"year": 2021,
"month": 2,
"day": 17
}
}
"""
struct WrapperModel: Codable {
var dueDate: DueDate
}
struct DueDate: Codable {
var year: Int
var month: Int
var day: Int
}
let jsonData = Data(json.utf8)
let decoder = JSONDecoder()
do {
let model = try decoder.decode(WrapperModel.self, from: jsonData)
print(model)
} catch {
print(error.localizedDescription)
}
我正在尝试获取 JSON 响应并将结果存储在一个变量中。在 Xcode 8 的 GM 版本发布之前,我已经在之前的 Swift 版本中使用了此代码的版本。我在 Whosebug 上查看了一些类似的帖子:
但是,这里传达的想法似乎不适用于这种情况。
如何正确解析 Swift 3 中的 JSON 响应? 在 Swift 3 中读取 JSON 的方式有什么变化吗?
下面是有问题的代码(在 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
.
'Any' has no subscript members
发生是因为编译器不知道中间对象是什么类型(例如 ["currently"]!["temperature"]
中的 currently
)并且因为您使用的是 NSDictionary
编译器之类的基础集合类型完全不知道类型。
另外在Swift 3中需要通知编译器关于所有下标对象的类型。
您必须将 JSON 序列化的结果转换为实际类型。
此代码仅使用 URLSession
和 Swift 本机类型
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) ")
}
关于jsonObject(with data
的注释:
许多(似乎全部)教程建议 .mutableContainers
或 .mutableLeaves
选项,这在 Swift 中完全是胡说八道。这两个选项是遗留 Objective-C 选项,用于将结果分配给 NSMutable...
对象。在 Swift 中,任何 var
iable 默认情况下都是可变的,传递任何这些选项并将结果分配给 let
常量根本没有效果。此外,大多数实现永远不会改变反序列化的 JSON。
在 Swift 中唯一有用的(罕见)选项是 .allowFragments
如果 JSON 根对象可以是值类型(String
, Number
, Bool
或 null
) 而不是其中一种集合类型 (array
或 dictionary
)。但通常会省略 options
参数,这意味着 没有选项 。
=========================================== ==============================
解析的一些一般注意事项JSON
JSON是一种排列整齐的文本格式。读取 JSON 字符串非常容易。 仔细阅读字符串。只有六种不同的类型 – 两种集合类型和四种值类型。
集合类型是
- 数组-JSON:方括号中的对象
[]
-Swift:[Any]
但在大多数情况下[[String:Any]]
- 字典 - JSON: 花括号中的对象
{}
- Swift:[String:Any]
值类型是
- String - JSON: 双引号中的任意值
"Foo"
, 甚至"123"
or"false"
– Swift:String
- 数字 - JSON:数值不是双引号
123
或123.0
– Swift:Int
或Double
- Bool - JSON:
true
orfalse
not in double quotes – Swift:true
或false
- null - JSON:
null
– Swift:NSNull
根据 JSON 规范,字典中的所有键都必须是 String
。
基本上总是推荐使用可选绑定来安全地解包可选
如果根对象是字典 ({}
) 将类型转换为 [String:Any]
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [String:Any] { ...
并通过键检索值(OneOfSupportedJSONTypes
是 JSON 集合或值类型,如上所述。)
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 let parsedData = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]], parsedData.count > 2,
let item = parsedData[2] as? OneOfSupportedJSONTypes {
print(item)
}
}
在极少数情况下,JSON 只是其中一种值类型,而不是集合类型,您必须传递 .allowFragments
选项并将结果转换为适当的值类型例如
if let parsedData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? String { ...
Apple 在 Swift 博客中发表了一篇综合文章:Working with JSON in Swift
=========================================== ==============================
在Swift 4+中,Codable
协议提供了一种更方便的方法来将JSON直接解析为结构/类.
例如问题中给定的 JSON 样本(稍作修改)
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 类型与上述相同。还有一些其他选项:
- 表示
URL
的字符串可以直接解码为URL
。 -
time
整数可以用dateDecodingStrategy
.secondsSince1970
. 解码为 - snaked_cased JSON 键可以转换为 camelCase 和
keyDecodingStrategy
.convertFromSnakeCase
Date
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)
}
其他 Codable 来源:
Xcode 8 Beta 6 for Swift 3 发生的一个重大变化是 id 现在导入为 Any
而不是 AnyObject
。
这意味着 parsedData
作为最有可能类型为 [Any:Any]
的字典返回。如果不使用调试器,我无法确切地告诉你你对 NSDictionary
的转换会做什么,但你看到的错误是因为 dict!["currently"]!
的类型为 Any
那么,你是如何解决这个问题的?从你引用它的方式来看,我假设 dict!["currently"]!
是一本字典,所以你有很多选择:
首先你可以这样做:
let currentConditionsDictionary: [String: AnyObject] = dict!["currently"]! as! [String: AnyObject]
这会给你一个字典对象,然后你可以查询值,这样你就可以像这样得到你的温度:
let currentTemperatureF = currentConditionsDictionary["temperature"] as! Double
或者,如果您愿意,可以排队:
let currentTemperatureF = (dict!["currently"]! as! [String: AnyObject])["temperature"]! as! Double
希望这有帮助,恐怕我还没有时间编写示例应用程序来测试它。
最后一点:最简单的方法可能是在开始时将 JSON 有效负载简单地转换为 [String: AnyObject]
。
let parsedData = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments) as! Dictionary<String, AnyObject>
更新之后的isConnectToNetwork-Function
,多亏了这个
我为它写了一个额外的方法:
import SystemConfiguration
func loadingJSON(_ link:String, postString:String, completionHandler: @escaping (_ JSONObject: AnyObject) -> ()) {
if(isConnectedToNetwork() == false){
completionHandler("-1" as AnyObject)
return
}
let request = NSMutableURLRequest(url: URL(string: link)!)
request.httpMethod = "POST"
request.httpBody = postString.data(using: String.Encoding.utf8)
let task = URLSession.shared.dataTask(with: request as URLRequest) { data, response, error in
guard error == nil && data != nil else { // check for fundamental networking error
print("error=\(error)")
return
}
if let httpStatus = response as? HTTPURLResponse , httpStatus.statusCode != 200 { // check for http errors
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(response)")
}
//JSON successfull
do {
let parseJSON = try JSONSerialization.jsonObject(with: data!, options: .allowFragments)
DispatchQueue.main.async(execute: {
completionHandler(parseJSON as AnyObject)
});
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
}
task.resume()
}
func isConnectedToNetwork() -> Bool {
var zeroAddress = sockaddr_in(sin_len: 0, sin_family: 0, sin_port: 0, sin_addr: in_addr(s_addr: 0), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress))
zeroAddress.sin_family = sa_family_t(AF_INET)
let defaultRouteReachability = withUnsafePointer(to: &zeroAddress) {
[=10=].withMemoryRebound(to: sockaddr.self, capacity: 1) {zeroSockAddress in
SCNetworkReachabilityCreateWithAddress(nil, zeroSockAddress)
}
}
var flags: SCNetworkReachabilityFlags = SCNetworkReachabilityFlags(rawValue: 0)
if SCNetworkReachabilityGetFlags(defaultRouteReachability!, &flags) == false {
return false
}
let isReachable = (flags.rawValue & UInt32(kSCNetworkFlagsReachable)) != 0
let needsConnection = (flags.rawValue & UInt32(kSCNetworkFlagsConnectionRequired)) != 0
let ret = (isReachable && !needsConnection)
return ret
}
现在您可以在您的应用程序中随时随地轻松调用它
loadingJSON("yourDomain.com/login.php", postString:"email=\(userEmail!)&password=\(password!)") { parseJSON in
if(String(describing: parseJSON) == "-1"){
print("No Internet")
} else {
if let loginSuccessfull = parseJSON["loginSuccessfull"] as? Bool {
//... do stuff
}
}
let str = "{\"names\": [\"Bob\", \"Tim\", \"Tina\"]}"
let data = str.data(using: String.Encoding.utf8, allowLossyConversion: false)!
do {
let json = try JSONSerialization.jsonObject(with: data, options: []) as! [String: AnyObject]
if let names = json["names"] as? [String]
{
print(names)
}
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
我正是为此而构建了 quicktype。只需粘贴您的示例 JSON,quicktype 就会为您的 API 数据生成此类型层次结构:
struct Forecast {
let hourly: Hourly
let daily: Daily
let currently: Currently
let flags: Flags
let longitude: Double
let latitude: Double
let offset: Int
let timezone: String
}
struct Hourly {
let icon: String
let data: [Currently]
let summary: String
}
struct Daily {
let icon: String
let data: [Datum]
let summary: String
}
struct Datum {
let precipIntensityMax: Double
let apparentTemperatureMinTime: Int
let apparentTemperatureLowTime: Int
let apparentTemperatureHighTime: Int
let apparentTemperatureHigh: Double
let apparentTemperatureLow: Double
let apparentTemperatureMaxTime: Int
let apparentTemperatureMax: Double
let apparentTemperatureMin: Double
let icon: String
let dewPoint: Double
let cloudCover: Double
let humidity: Double
let ozone: Double
let moonPhase: Double
let precipIntensity: Double
let temperatureHigh: Double
let pressure: Double
let precipProbability: Double
let precipIntensityMaxTime: Int
let precipType: String?
let sunriseTime: Int
let summary: String
let sunsetTime: Int
let temperatureMax: Double
let time: Int
let temperatureLow: Double
let temperatureHighTime: Int
let temperatureLowTime: Int
let temperatureMin: Double
let temperatureMaxTime: Int
let temperatureMinTime: Int
let uvIndexTime: Int
let windGust: Double
let uvIndex: Int
let windBearing: Int
let windGustTime: Int
let windSpeed: Double
}
struct Currently {
let precipProbability: Double
let humidity: Double
let cloudCover: Double
let apparentTemperature: Double
let dewPoint: Double
let ozone: Double
let icon: String
let precipIntensity: Double
let temperature: Double
let pressure: Double
let precipType: String?
let summary: String
let uvIndex: Int
let windGust: Double
let time: Int
let windBearing: Int
let windSpeed: Double
}
struct Flags {
let sources: [String]
let isdStations: [String]
let units: String
}
它还生成无依赖编组代码,以将 JSONSerialization.jsonObject
的 return 值哄骗到 Forecast
,包括一个方便的构造函数,它接受 JSON 字符串,因此您可以快速解析强类型 Forecast
值并访问其字段:
let forecast = Forecast.from(json: jsonString)!
print(forecast.daily.data[0].windGustTime)
您可以使用 npm i -g quicktype
或 use the web UI 从 npm 安装 quicktype 以将完整的生成代码粘贴到您的 playground 中。
问题出在 API 交互方法上。 JSON 解析仅在语法上发生了变化。主要问题在于获取数据的方式。您使用的是同步获取数据的方式。这并非在所有情况下都有效。您应该使用的是一种异步方式来获取数据。这样就得通过API请求数据,然后等待它响应数据。您可以使用 URL 会话和 Alamofire
等第三方库来实现此目的。下面是 URL 会话方法的代码。
let urlString = "https://api.forecast.io/forecast/apiKey/37.5673776,122.048951"
let url = URL.init(string: urlString)
URLSession.shared.dataTask(with:url!) { (data, response, error) in
guard error == nil else {
print(error)
}
do {
let Data = try JSONSerialization.jsonObject(with: data!) as! [String:Any]
// Note if your data is coming in Array you should be using [Any]()
//Now your data is parsed in Data variable and you can use it normally
let currentConditions = Data["currently"] as! [String:Any]
print(currentConditions)
let currentTemperatureF = currentConditions["temperature"] as! Double
print(currentTemperatureF)
} catch let error as NSError {
print(error)
}
}.resume()
Swift 具有强大的类型推断。让我们摆脱“if let”或“guard let”样板文件并使用函数式方法强制解包:
- 这是我们的JSON。我们可以使用可选的 JSON 或通常的。我在我们的例子中使用可选的:
let json: Dictionary<String, Any>? = ["current": ["temperature": 10]]
- 辅助函数。我们只需要编写一次,然后在任何字典中重复使用:
/// Curry
public func curry<A, B, C>(_ f: @escaping (A, B) -> C) -> (A) -> (B) -> C {
return { a in
{ f(a, [=11=]) }
}
}
/// Function that takes key and optional dictionary and returns optional value
public func extract<Key, Value>(_ key: Key, _ json: Dictionary<Key, Any>?) -> Value? {
return json.flatMap {
cast([=11=][key])
}
}
/// Function that takes key and return function that takes optional dictionary and returns optional value
public func extract<Key, Value>(_ key: Key) -> (Dictionary<Key, Any>?) -> Value? {
return curry(extract)(key)
}
/// Precedence group for our operator
precedencegroup RightApplyPrecedence {
associativity: right
higherThan: AssignmentPrecedence
lowerThan: TernaryPrecedence
}
/// Apply. g § f § a === g(f(a))
infix operator § : RightApplyPrecedence
public func §<A, B>(_ f: (A) -> B, _ a: A) -> B {
return f(a)
}
/// Wrapper around operator "as".
public func cast<A, B>(_ a: A) -> B? {
return a as? B
}
- 这是我们的魔法 - 提取值:
let temperature = (extract("temperature") § extract("current") § json) ?? NSNotFound
只需一行代码,无需强制解包或手动类型转换。此代码在 playground 中有效,因此您可以复制并检查它。这是一个实现 on GitHub.
这是解决您问题的另一种方法。因此,请查看以下解决方案。希望对你有帮助。
let str = "{\"names\": [\"Bob\", \"Tim\", \"Tina\"]}"
let data = str.data(using: String.Encoding.utf8, allowLossyConversion: false)!
do {
let json = try JSONSerialization.jsonObject(with: data, options: []) as! [String: AnyObject]
if let names = json["names"] as? [String] {
print(names)
}
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
{
"User":[
{
"FirstUser":{
"name":"John"
},
"Information":"XY",
"SecondUser":{
"name":"Tom"
}
}
]
}
如果我使用以前的 json 创建模型 使用此 link [博客]:http://www.jsoncafe.com 生成 Codable 结构或任何格式
型号
import Foundation
struct RootClass : Codable {
let user : [Users]?
enum CodingKeys: String, CodingKey {
case user = "User"
}
init(from decoder: Decoder) throws {
let values = try? decoder.container(keyedBy: CodingKeys.self)
user = try? values?.decodeIfPresent([Users].self, forKey: .user)
}
}
struct Users : Codable {
let firstUser : FirstUser?
let information : String?
let secondUser : SecondUser?
enum CodingKeys: String, CodingKey {
case firstUser = "FirstUser"
case information = "Information"
case secondUser = "SecondUser"
}
init(from decoder: Decoder) throws {
let values = try? decoder.container(keyedBy: CodingKeys.self)
firstUser = try? FirstUser(from: decoder)
information = try? values?.decodeIfPresent(String.self, forKey: .information)
secondUser = try? SecondUser(from: decoder)
}
}
struct SecondUser : Codable {
let name : String?
enum CodingKeys: String, CodingKey {
case name = "name"
}
init(from decoder: Decoder) throws {
let values = try? decoder.container(keyedBy: CodingKeys.self)
name = try? values?.decodeIfPresent(String.self, forKey: .name)
}
}
struct FirstUser : Codable {
let name : String?
enum CodingKeys: String, CodingKey {
case name = "name"
}
init(from decoder: Decoder) throws {
let values = try? decoder.container(keyedBy: CodingKeys.self)
name = try? values?.decodeIfPresent(String.self, forKey: .name)
}
}
解析
do {
let res = try JSONDecoder().decode(RootClass.self, from: data)
print(res?.user?.first?.firstUser?.name ?? "Yours optional value")
} catch {
print(error)
}
Swift 5
无法从您的 api 获取数据。
解析 json 的最简单方法是使用 Decodable
协议。或者 Codable
(Encodable & Decodable
)。
例如:
let json = """
{
"dueDate": {
"year": 2021,
"month": 2,
"day": 17
}
}
"""
struct WrapperModel: Codable {
var dueDate: DueDate
}
struct DueDate: Codable {
var year: Int
var month: Int
var day: Int
}
let jsonData = Data(json.utf8)
let decoder = JSONDecoder()
do {
let model = try decoder.decode(WrapperModel.self, from: jsonData)
print(model)
} catch {
print(error.localizedDescription)
}