Swift 错误类型服务器响应来自 REST API 的错误输入

Posted

技术标签:

【中文标题】Swift 错误类型服务器响应来自 REST API 的错误输入【英文标题】:Swift error type server response for wrong input from rest API 【发布时间】:2020-02-10 18:28:43 【问题描述】:

希望你一切安好。我有个问题。我有一个带有电子邮件和密码的简单登录页面,还有一个类似的用户对象

// MARK: - UserModel
struct UserModel: Codable 
    let error: Bool
    let desc: String
    let user: User
    let token: String


// MARK: - User
struct User: Codable 
    let id: Int
    let email, firstName, lastName, lang: String
    let status: Int
    let referer, star: String?
    let phone: String?
    let ip: String?
    let birth, idNumber: String?
    let regionID: String?
    let createdAt, updatedAt: String

    enum CodingKeys: String, CodingKey 
        case id, email
        case firstName = "first_name"
        case lastName = "last_name"
        case lang, status, referer, star, phone, ip, birth
        case idNumber = "id_number"
        case regionID = "region_id"
        case createdAt, updatedAt
    

返回类型是上一个(UserModel)。如果用户输入了他/她的凭据,则没有问题。但是,如果他/她输入了错误的凭据,麻烦就开始了。我无法解析来自服务器的返回值。总是给我那一行的错误。

控制台输出为:

Rentover[2343:150674] Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.typeMismatch(Swift.Bool, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "error", intValue: nil)], debugDescription: "Expected to decode Bool but found a dictionary instead.", underlyingError: nil)): file

这是我的登录请求功能。为了简单起见,我使用了 codable。

class func requestLogIn(router: Router, completion: @escaping (Result<UserModel, Error>) -> ()) 

    guard let url = setUrlComponents(router: router).url else  return 
    var urlRequest = URLRequest(url: url)
    urlRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
    urlRequest.httpMethod = router.method

    if router.method == "POST"
        let model = LoginModel(email: router.parameters[0], password: router.parameters[1])
        urlRequest.httpBody = try? JSONEncoder().encode(model)
    

    let dataTask = URLSession.shared.dataTask(with: urlRequest)  data, response, error in

        guard error == nil else 
            print(error?.localizedDescription)
            return
        
        guard response != nil else 
            print("no response")
            return
        
        guard let data = data else 
            print("no data")
            return
        

        let responseObject = try! JSONDecoder().decode(UserModel.self, from: data)
        print(responseObject.user)
        DispatchQueue.main.async 
            completion(.success(responseObject))
        


    
    dataTask.resume()

这是我的错误结构。

struct LogInError: Codable, Error
    let error: Bool
    let desc: String
    let fields: [String] ----> 'Edit here old: let fileds: [String' 

最后我真正的调用函数是这样的

    NetworkService.requestLogIn(router: Router.login(email: nameTextField.text!, passowrd: passwordTextField.text!))  (result) in
        switch result 
        case .success(let userModel):
            print("RESULT SUCCESS")
            print("Hello \(userModel.user.firstName)")

            let selectedVC = UIUtils.checkUserStatus(status: userModel.user.status)
            self.navigationController?.modalPresentationStyle = .fullScreen
            self.navigationController?.pushViewController(selectedVC, animated: true)
        case .failure(let error):
            print("RESULT FAILED")
            print(error)
        
    

我按照that medium 链接创建了我的路由器和网络服务。如果您能帮助我解决这个问题,我将非常高兴和感激。或者给我一些关于网络 API 和使用的建议。

[编辑来自服务器的错误响应] 我的请求和响应消息体框架也是这样的:

祝你有美好的一天。和良好的编码。

【问题讨论】:

该错误表示如果发生错误,键 error 的值是一个字典。因此,LogInError 似乎与 JSON 结构不匹配。请添加 JSON 字符串。并且永远不要在Decodable 上下文中使用try!。始终catch 并处理错误。 你好@vadian,不幸的是我的JSON结构是正确的。我正在使用邮递员。我的问题是,当响应是来自我的休息服务的错误时。如何检测和解码针对特定错误的错误响应。 如我所说,添加错误返回的 JSON。 @vadian 我编辑了这个问题。你能再看一遍吗? 归档!= 字段。尝试!意思是“如果发生错误,程序崩溃并销毁任何错误的证据”。 try 表示“如果发生错误,返回可以打印的错误”。 【参考方案1】:

要解码两个不同的 JSON 字符串,一个方便的解决方案是使用关联类型的枚举,因为它可以非常具有描述性地表示 successfailure 情况。

首先它解码常见的error 密钥,然后解码UserModelLogInError

enum Response : Decodable 
    case success(UserModel), failure(LogInError)

    private enum CodingKeys : String, CodingKey  case error 

    init(from decoder: Decoder) throws 
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let hasError = try container.decode(Bool.self, forKey: .error)
        if hasError 
            let errorContainer = try decoder.singleValueContainer()
            let errorData = try errorContainer.decode(LogInError.self)
            self = .failure(errorData)
         else 
            let successContainer = try decoder.singleValueContainer()
            let successData = try successContainer.decode(UserModel.self)
            self = .success(successData)
        
    

使用它

class func requestLogIn(router: Router, completion: @escaping (Result<Response, Error>) -> ()) 

    ...

    do 
        let responseObject = try JSONDecoder().decode(Response.self, from: data)
        print(responseObject)
        DispatchQueue.main.async 
            completion(.success(responseObject))
        
     catch 
        DispatchQueue.main.async 
            completion(.failure(error))
        
    

NetworkService.requestLogIn(router: Router.login(email: nameTextField.text!, passowrd: passwordTextField.text!))  (response) in
    switch response 
    case .success(let result):
        switch result 
            case .success(let userModel): 
                print("RESULT SUCCESS")
                print("Hello \(userModel.user.firstName)")

                let selectedVC = UIUtils.checkUserStatus(status: userModel.user.status)
                self.navigationController?.modalPresentationStyle = .fullScreen
                self.navigationController?.pushViewController(selectedVC, animated: true)
            case .failure(let errorData): 
                print(errorData)
        

    case .failure(let error):
        print("RESULT FAILED")
        print(error)
    

LoginError 声明为标准的可解码结构

struct LogInError: Decodable 

【讨论】:

我实现了你的答案并且它有效。也非常合乎逻辑,非常感谢您的时间。只是您回答中的最后一个问题,您将 LogInError:Codable 更改为 LogInError:Decodable。原因是该结构不会与任何发布请求(任何编码过程)相关? LoginErrorLoginModel 不一样。 是的,所以对于LoginError不需要保持从Decodable继承吗? LoginError 必须符合 Decodable 不像 LoginModel 非常感谢先生!祝你有美好的一天。

以上是关于Swift 错误类型服务器响应来自 REST API 的错误输入的主要内容,如果未能解决你的问题,请参考以下文章

强类型对象与普通 XML 作为来自 REST API 服务的响应?哪个最好

Grails REST Client Builder 在处理来自 Jersey Web 服务的响应时收到 JSON 反序列化错误

错误响应上的 REST-API 不同的内容类型

Swift 4 - 如何在 UIAlert 中显示来自 JSON Alamofire 的错误响应

iOS应用swift 3中的谷歌翻译rest api错误403

在 Django 中为来自客户端的请求和来自服务器的响应(REST API)压缩 JSON 有效负载。