簡體   English   中英

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的新結構,還是在現有登錄結構中需要它?

您需要為成功錯誤情況創建單獨的Codable模型。 然后將它們組合成一個可用於解析的 model。

Login model:

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 model:

struct ErrorResponse: Decodable {
    let message: String
    let errors: Errors
}

struct Errors: Decodable {
    let email, password: [String]
}

像這樣將LoginErrorResponse模型組合到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 model 像這樣解析您的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 數據是動態的,可編碼的主要是 static。

您通常不會擁有不可變的 JSON 響應,見鬼,API 本身在開發過程中一直在變化。

您還需要通過對特殊編碼鍵的額外處理來遞歸地創建 Codable 結構。

Codable 僅在小規模或自動生成時才有意義。

以另一個答案為例,您需要為 JSON 響應定義 5 種類型。 它們中的大多數是不可重復使用的。

對於 JSON,它是var j = JSON(data)

guard let user = j[.user].string else {// error handling}...

您將字符串user替換為枚舉 case case user

JSON,可重復使用,編碼密鑰.user可重復使用。

由於 JSON 是嵌套的,您可以根據用例將user.id替換為j[.user][.id].stringValuej[.user][.id].intValue

再次.user.id可以添加到編碼鍵枚舉中以可重用。

您還可以靈活地適應運行時的變化。

具有諷刺意味的是,在我看來,應該將 Codable 用於 Encodable,而不是 Decodable。

因為當你編碼時,你可以完全控制它的類型; 當您解碼 json 響應時,您將受到后端的擺布。

Swift 類型系統很棒,但您並不總是在每一步都需要確切的類型。

JSON 在深度嵌套的 json 響應中非常寶貴,例如; j[.result][.data][0][.user][.hobbies][0][.hobbyName].stringValue 完成后,您仍在編寫第一級 Codable 結構。

在這里分享這個作為比較,這樣更多的人可以理解這是多么強大。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM