【问题标题】:Swift struct optional valuesSwift 结构可选值
【发布时间】:2020-07-08 12:11:02
【问题描述】:

我正在向我的 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 的新结构,还是在现有登录结构中需要它?

【问题讨论】:

    标签: ios json swift swiftui combine


    【解决方案1】:

    您需要为 成功错误 情况创建单独的 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]
    }
    

    像这样将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 模型解析您的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()
    

    【讨论】:

    • 另一种选择是使用(现有的)Result 类型。
    • 还有一个选项,当你使用 Result 类型时。
    • 啊,谢谢!但是如何从响应中获取用户日期?如果我打印它是有效的,我会看到所有信息。例如,我怎样才能只打印assessToken?
    • @PGDev 非常感谢,我终于明白了!
    【解决方案2】:

    或者使用 SwiftyJSON。

    如果您担心第三方依赖。自己写一个。

    SwiftyJSON 基本上是一种结构,即 JSON,用于重构常用操作。

    背后的想法很简单:

    JSON 数据是动态的,Codable 大部分是静态的。

    你通常不会享受不可变的 JSON 响应,见鬼,API 本身在开发过程中一直在变化。

    您还需要通过对特殊编码键的额外处理递归地创建可编码结构。

    可编码仅在小规模或自动生成时才有意义。

    以另一个答案为例,您需要为 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].stringValuej[.user][.id].intValue

    同样.user.id 可以添加到编码密钥枚举中以便可重用。

    您还可以灵活地适应运行时的变化。

    具有讽刺意味的是,在我看来,应该将 Codable 用于 Encodable,而不是 Decodable。

    因为当你编码时,你可以完全控制它的类型;当您解码 json 响应时,您将受到后端的摆布。

    Swift 类型系统很棒,但您并不总是在每一步都需要确切的类型。

    JSON 在深度嵌套的 json 响应中是无价的,例如; j[.result][.data][0][.user][.hobbies][0][.hobbyName].stringValue。完成后,您仍在编写第一级 Codable 结构。

    在此处分享此作为比较,以便更多人了解它的强大功能。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-11-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多