Swift 结构可选值
Swift struct optional values
我正在向我的 SwiftUI 项目添加一个简单的登录系统。只是我不太明白。
问题出在哪里,当用户想要登录时,它可以正常工作。我从服务器收到此响应:
"user": {
"id": 6,
"name": "test",
"email": "test@test.com",
"email_verified_at": null,
"created_at": "2020-07-02T09:37:54.000000Z",
"updated_at": "2020-07-02T09:37:54.000000Z"
},
"assessToken": "test-token"
}
但是当出现问题时,服务器会显示如下错误消息:
"message": "The given data was invalid.",
"errors": {
"email": [
"The email field is required."
],
"password": [
"The password field is required."
]
}
}
我怎样才能确保将这些信息解析成一个结构。目前看起来是这样的。
// This file was generated from JSON Schema using quicktype, do not modify it directly.
// To parse the JSON, add this file to your project and do:
//
// let welcome = try? newJSONDecoder().decode(Welcome.self, from: jsonData)
import Foundation
// MARK: - Welcome
struct Login: Codable {
let user: User
let assessToken: String
}
// MARK: - User
struct User: Codable {
let id: Int
let name, email: String
let emailVerifiedAt: JSONNull?
let createdAt, updatedAt: String
enum CodingKeys: String, CodingKey {
case id, name, email
case emailVerifiedAt = "email_verified_at"
case createdAt = "created_at"
case updatedAt = "updated_at"
}
}
// MARK: - Encode/decode helpers
class JSONNull: Codable, Hashable {
public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool {
return true
}
public var hashValue: Int {
return 0
}
public init() {}
public required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if !container.decodeNil() {
throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}
我现在是这样做的:
class HttpAuth: ObservableObject{
var didChange = PassthroughSubject<HttpAuth, Never>()
var authenticated = false{
didSet{
didChange.send(self)
}
}
func checkDetails(email: String, password: String){
guard let url = URL(string: "https://test.ngrok.io/api/login") else {
return
}
let body : [String : String] = ["email" : email, "password": password]
let finalBody = try! JSONSerialization.data(withJSONObject: body)
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = finalBody
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else {return}
let finalData = try! JSONDecoder().decode(Login.self, from: data)
print(finalData)
}.resume()
}
}
例如,我是否必须创建一个名为 LoginError
的新结构,或者我是否需要在现有登录结构中使用它?
您需要为成功和错误[=39]创建单独的Codable
模型=] 例。然后将它们组合成一个可用于解析的模型。
Login
型号:
struct Login: Decodable {
let user: User
let assessToken: String
}
struct User: Decodable {
let id: Int
let name, email: String
let emailVerifiedAt: String?
let createdAt, updatedAt: String
}
Error
型号:
struct ErrorResponse: Decodable {
let message: String
let errors: Errors
}
struct Errors: Decodable {
let email, password: [String]
}
将Login
和ErrorResponse
模型合并成Response
,
enum Response: Decodable {
case success(Login)
case failure(ErrorResponse)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
let user = try container.decode(Login.self)
self = .success(user)
} catch {
let error = try container.decode(ErrorResponse)
self = .failure(error)
}
}
}
现在,使用 Response
模型来解析您的 data
,
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else {return}
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let response = try decoder.decode(Response.self, from: data)
switch response {
case .success(let login):
let assessToken = login.assessToken
print(assessToken)
//get any required data from login here..
case .failure(let error):
let message = error.message
}
} catch {
print(error)
}
}.resume()
或使用SwiftyJSON.
如果您担心第三方依赖。自己写一个。
SwiftyJSON 基本上是一个结构,即 JSON,用于重构常见操作。
背后的想法很简单:
JSON 数据是动态的,Codable 大部分是静态的。
你通常没有不可变的 JSON 响应的奢侈,地狱,API 本身在开发过程中一直在变化。
您还需要通过对特殊编码键的额外处理递归地创建 Codable 结构。
Codable 只有在小规模或自动生成时才有意义。
以另一个答案为例,您需要为JSON 响应定义5 种类型。其中大部分不可重复使用。
和JSON是var j = JSON(data)
.
guard let user = j[.user].string else {// error handling} ...
将字符串 user
替换为枚举大小写 case user
.
JSON,可重复使用,编码密钥.user
可重复使用。
由于 JSON 是嵌套的,您可以根据用例将 user.id
替换为 j[.user][.id].stringValue
或 j[.user][.id].intValue
。
再次 .user
,.id
可以添加到编码键枚举中以供重复使用。
您还可以非常灵活地适应 运行 时间变化。
具有讽刺意味的是,在我看来,应该将 Codable 用于 Encodable,而不是用于 Decodable。
因为当你编码的时候,你可以完全控制它的类型;当您解码 json 响应时,您将受到后端的支配。
Swift 类型系统很棒,但您并不总是需要在每一步都使用准确的类型。
JSON 在深度嵌套的 json 响应中是无价的,例如; j[.result][.data][0][.user][.hobbies][0][.hobbyName].stringValue
。当我完成时,您仍在编写第一级 Codable 结构。
在此作为比较分享此内容,以便更多人了解它的强大功能。
我正在向我的 SwiftUI 项目添加一个简单的登录系统。只是我不太明白。
问题出在哪里,当用户想要登录时,它可以正常工作。我从服务器收到此响应:
"user": {
"id": 6,
"name": "test",
"email": "test@test.com",
"email_verified_at": null,
"created_at": "2020-07-02T09:37:54.000000Z",
"updated_at": "2020-07-02T09:37:54.000000Z"
},
"assessToken": "test-token"
}
但是当出现问题时,服务器会显示如下错误消息:
"message": "The given data was invalid.",
"errors": {
"email": [
"The email field is required."
],
"password": [
"The password field is required."
]
}
}
我怎样才能确保将这些信息解析成一个结构。目前看起来是这样的。
// This file was generated from JSON Schema using quicktype, do not modify it directly.
// To parse the JSON, add this file to your project and do:
//
// let welcome = try? newJSONDecoder().decode(Welcome.self, from: jsonData)
import Foundation
// MARK: - Welcome
struct Login: Codable {
let user: User
let assessToken: String
}
// MARK: - User
struct User: Codable {
let id: Int
let name, email: String
let emailVerifiedAt: JSONNull?
let createdAt, updatedAt: String
enum CodingKeys: String, CodingKey {
case id, name, email
case emailVerifiedAt = "email_verified_at"
case createdAt = "created_at"
case updatedAt = "updated_at"
}
}
// MARK: - Encode/decode helpers
class JSONNull: Codable, Hashable {
public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool {
return true
}
public var hashValue: Int {
return 0
}
public init() {}
public required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if !container.decodeNil() {
throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}
我现在是这样做的:
class HttpAuth: ObservableObject{
var didChange = PassthroughSubject<HttpAuth, Never>()
var authenticated = false{
didSet{
didChange.send(self)
}
}
func checkDetails(email: String, password: String){
guard let url = URL(string: "https://test.ngrok.io/api/login") else {
return
}
let body : [String : String] = ["email" : email, "password": password]
let finalBody = try! JSONSerialization.data(withJSONObject: body)
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = finalBody
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else {return}
let finalData = try! JSONDecoder().decode(Login.self, from: data)
print(finalData)
}.resume()
}
}
例如,我是否必须创建一个名为 LoginError
的新结构,或者我是否需要在现有登录结构中使用它?
您需要为成功和错误[=39]创建单独的Codable
模型=] 例。然后将它们组合成一个可用于解析的模型。
Login
型号:
struct Login: Decodable {
let user: User
let assessToken: String
}
struct User: Decodable {
let id: Int
let name, email: String
let emailVerifiedAt: String?
let createdAt, updatedAt: String
}
Error
型号:
struct ErrorResponse: Decodable {
let message: String
let errors: Errors
}
struct Errors: Decodable {
let email, password: [String]
}
将Login
和ErrorResponse
模型合并成Response
,
enum Response: Decodable {
case success(Login)
case failure(ErrorResponse)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
let user = try container.decode(Login.self)
self = .success(user)
} catch {
let error = try container.decode(ErrorResponse)
self = .failure(error)
}
}
}
现在,使用 Response
模型来解析您的 data
,
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else {return}
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let response = try decoder.decode(Response.self, from: data)
switch response {
case .success(let login):
let assessToken = login.assessToken
print(assessToken)
//get any required data from login here..
case .failure(let error):
let message = error.message
}
} catch {
print(error)
}
}.resume()
或使用SwiftyJSON.
如果您担心第三方依赖。自己写一个。
SwiftyJSON 基本上是一个结构,即 JSON,用于重构常见操作。
背后的想法很简单:
JSON 数据是动态的,Codable 大部分是静态的。
你通常没有不可变的 JSON 响应的奢侈,地狱,API 本身在开发过程中一直在变化。
您还需要通过对特殊编码键的额外处理递归地创建 Codable 结构。
Codable 只有在小规模或自动生成时才有意义。
以另一个答案为例,您需要为JSON 响应定义5 种类型。其中大部分不可重复使用。
和JSON是var j = JSON(data)
.
guard let user = j[.user].string else {// error handling} ...
将字符串 user
替换为枚举大小写 case user
.
JSON,可重复使用,编码密钥.user
可重复使用。
由于 JSON 是嵌套的,您可以根据用例将 user.id
替换为 j[.user][.id].stringValue
或 j[.user][.id].intValue
。
再次 .user
,.id
可以添加到编码键枚举中以供重复使用。
您还可以非常灵活地适应 运行 时间变化。
具有讽刺意味的是,在我看来,应该将 Codable 用于 Encodable,而不是用于 Decodable。
因为当你编码的时候,你可以完全控制它的类型;当您解码 json 响应时,您将受到后端的支配。
Swift 类型系统很棒,但您并不总是需要在每一步都使用准确的类型。
JSON 在深度嵌套的 json 响应中是无价的,例如; j[.result][.data][0][.user][.hobbies][0][.hobbyName].stringValue
。当我完成时,您仍在编写第一级 Codable 结构。
在此作为比较分享此内容,以便更多人了解它的强大功能。