Correctly Parsing JSON in Swift 3

我试图获取一个 JSON 响应并将结果存储在一个变量中。在之前的 Swift 版本中,我已经有了这个代码的版本,直到 Xcode 8的 GM 版本发布。我看了一些关于 StackOverflow 的类似文章: Swift 2解析 JSON-无法下标类型为‘ AnyObject’的值Swift 3中的 JSON 解析

然而,这里传达的想法似乎并不适用于这种情况。

如何正确解析 Swift 3中的 JSON 响应? 在 Swift 3中 JSON 的读取方式有什么变化吗?

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

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]
136686 次浏览

首先,永远不要从远程 URL 同步加载数据始终使用类似于 URLSession的异步方法。

“ Any”没有下标成员

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中,需要告知编译器 所有下标对象的类型。

您必须将 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选项,这是完全无意义的迅速。这两个选项是遗留的 Objective-C 选项,用于将结果分配给 NSMutable...对象。在 Swift 中,任何 variable 在默认情况下都是可变的,传递任何这些选项并将结果分配给 let常量都没有任何效果。此外,大多数实现从来不会对反序列化的 JSON 进行变异。

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 别无选择.

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

解析 JSON 的一些常规注意事项

JSON 是一种排列良好的文本格式。读取 JSON 字符串非常容易。仔细阅读字符串.只有六种不同的类型——两种集合类型和四种值类型。


集合类型为

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

值类型是

  • 字符串 -JSON: 双引号 "Foo"中的任何值,甚至 "123""false"-Swift: String
  • Number - JSON: numeric values 没有 in double quotes 123 or 123.0 – Swift: Int or Double
  • Bool -JSON: truefalse 没有双引号-Swift: truefalse
  • Null -JSON: null-Swift: NSNull

根据 JSON 规范,字典中的所有键都必须是 String


基本上,总是建议使用可选绑定来安全地展开可选项

如果根对象是 dictionary ({}) ,则将类型转换为 [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)
}
}

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 Blog 上发表了一篇全面的文章: 在 Swift 中使用 JSON


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

在 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
  • 可以使用 keyDecodingStrategy .convertFromSnakeCase将 Snake _ case JSON 键转换为 camelCase

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

其他编码来源:

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

Hopefully this helps, I'm afraid I have not had time to write a sample app to test it.

One final note: the easiest thing to do, might be to simply cast the JSON payload into [String: AnyObject] right at the start.

let parsedData = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments) as! Dictionary<String, AnyObject>

之后更新了 isConnectToNetwork-Function,感谢这个 post

我为它写了一个额外的方法:

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) {
$0.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)")
}

我建立 快速打字就是为了这个目的。只需粘贴您的示例 JSON 并快速键入就可以为您的 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的返回值诱骗到 Forecast中,包括一个方便的构造函数,该构造函数接受一个 JSON 字符串,这样您就可以快速解析一个强类型的 Forecast值并访问它的字段:

let forecast = Forecast.from(json: jsonString)!
print(forecast.daily.data[0].windGustTime)

你可以用 npm i -g quicktype或者 使用网页用户界面从 npm 安装 Quick type 来获得完整的生成代码粘贴到你的操场上。

问题在于 API 交互方法。JSON 解析只在语法上有所改变。主要问题是获取数据的方式。您正在使用的是获取数据的同步方法。不是每个案子都能成功的。您应该使用异步方法来获取数据。通过这种方式,您必须通过 API 请求数据,并等待数据响应。您可以使用 URL 会话和第三方库(如 Alamofire)来实现这一点。下面是 URL Session 方法的代码。

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 has a powerful type inference. Lets get rid of "if let" or "guard let" boilerplate and force unwraps using functional approach:

  1. 这是我们的 JSON。我们可以使用可选的 JSON 或通常的。我在我们的例子中使用可选的:
let json: Dictionary<String, Any>? = ["current": ["temperature": 10]]
  1. Helper 函数。我们只需要编写它们一次,然后在任何字典中重用它们:
/// Curry
public func curry<A, B, C>(_ f: @escaping (A, B) -> C) -> (A) -> (B) -> C {
return { a in
{ f(a, $0) }
}
}


/// 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($0[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
}
  1. 这就是我们的魔法——提取价值:
let temperature = (extract("temperature") § extract("current") § json) ?? NSNotFound

只有一行代码,没有力量展开或手动类型铸造。这个代码在操场上工作,所以你可以复制和检查它。下面是一个实现 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 创建模型 Using this link [blog]: http://www.jsoncafe.com to generate Codable structure or Any Format

模特

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 Cant fetch data from your 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)
}