快速将动态服务器响应转换为模型类

问题描述

以下是服务器响应处理父结构...

struct ServerResponse<T: Codable>: Codable {

    let status: Bool?
    let message: String?
    let data: T?

    enum CodingKeys: String,CodingKey {
        case status = "status"
        case message = "message"
        case data = "data"
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        status = try values.decodeIfPresent(Bool.self,forKey: .status)
        message = try values.decodeIfPresent(String.self,forKey: .message)
        data = try values.decodeIfPresent(T.self,forKey: .data)
    }
}

AppUserResponse结构:

struct AppUserResponse: Codable  {

    let accesstoken : String?
    let askForMobileNo : Int?
    let tokenType : String?
    let user : AppUser?

    enum CodingKeys: String,CodingKey {
        case accesstoken = "access_token"
        case askForMobileNo = "ask_for_mobile_no"
        case tokenType = "token_type"
        case user = "user"
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        accesstoken = try values.decodeIfPresent(String.self,forKey: .accesstoken)
        askForMobileNo = try values.decodeIfPresent(Int.self,forKey: .askForMobileNo)
        tokenType = try values.decodeIfPresent(String.self,forKey: .tokenType)
        user = try values.decodeIfPresent(AppUser.self,forKey: .user)
    }

}

struct AppUser: Codable {

    let createdAt : String?
    let devicetoken : String?
    let deviceType : String?
    let email : String?
    let emailVerifiedAt : String?
    let firstName : String?
    let id : Int?
    let lastName : String?
    let mobile_no : String?
    let mobiLeverified : Int?
    let mobiLeverifiedAt : String?
    let provider : String?
    let providerId : String?
    let status : Int?
    let updatedAt : String?

    enum CodingKeys: String,CodingKey {
        
        case createdAt = "created_at"
        case devicetoken = "device_token"
        case deviceType = "device_type"
        case email = "email"
        case emailVerifiedAt = "email_verified_at"
        case firstName = "first_name"
        case id = "id"
        case lastName = "last_name"
        case mobile_no = "mobile_no"
        case mobiLeverified = "mobile_verified"
        case mobiLeverifiedAt = "mobile_verified_at"
        case provider = "provider"
        case providerId = "provider_id"
        case status = "status"
        case updatedAt = "updated_at"
       
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        createdAt = try values.decodeIfPresent(String.self,forKey: .createdAt)
        devicetoken = try values.decodeIfPresent(String.self,forKey: .devicetoken)
        deviceType = try values.decodeIfPresent(String.self,forKey: .deviceType)
        email = try values.decodeIfPresent(String.self,forKey: .email)
        emailVerifiedAt = try values.decodeIfPresent(String.self,forKey: .emailVerifiedAt)
        firstName = try values.decodeIfPresent(String.self,forKey: .firstName)
        id = try values.decodeIfPresent(Int.self,forKey: .id)
        lastName = try values.decodeIfPresent(String.self,forKey: .lastName)
        mobile_no = try values.decodeIfPresent(String.self,forKey: .mobile_no)
        mobiLeverified = try values.decodeIfPresent(Int.self,forKey: .mobiLeverified)
        mobiLeverifiedAt = try values.decodeIfPresent(String.self,forKey: .mobiLeverifiedAt)
        provider = try values.decodeIfPresent(String.self,forKey: .provider)
        providerId = try values.decodeIfPresent(String.self,forKey: .providerId)
        status = try values.decodeIfPresent(Int.self,forKey: .status)
        updatedAt = try values.decodeIfPresent(String.self,forKey: .updatedAt)
    }

}

TempuserResponse结构

struct TempuserResponse : Codable {

    let askForMobileNo : Int?
    let provider : String?
    let providerId : String?
    let tempuser : Tempuser?

    enum CodingKeys: String,CodingKey {
        case askForMobileNo = "ask_for_mobile_no"
        case provider = "provider"
        case providerId = "provider_id"
        case tempuser = "temp_user"
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        askForMobileNo = try values.decodeIfPresent(Int.self,forKey: .askForMobileNo)
        provider = try values.decodeIfPresent(String.self,forKey: .providerId)
        tempuser = try values.decodeIfPresent(Tempuser.self,forKey: .tempuser)
    }

}


struct Tempuser : Codable {

    let email : String?
    let name : String?

    enum CodingKeys: String,CodingKey {
        case email = "email"
        case name = "name"
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        email = try values.decodeIfPresent(String.self,forKey: .email)
        name = try values.decodeIfPresent(String.self,forKey: .name)
    }

}

问题

  • 在应用程序中,我们有2个按钮,分别是1. FB登录和2. Google登录

  • 当我尝试使用新用户登录时,服务器返回此响应(此时用户尚未注册,因此我在“数据”中获取“ tempuser”)

        {
          "status": true,"message": "User social email address verified,mobile number unverified!.","data": {
            "temp_user": {
              "name": "dhaval solanki","email": "dhaval.sassyinfotech@gmail.com"
            },"provider": "google","provider_id": "112620316711299944315","ask_for_mobile_no": 1
          }
        }
    
  • 注册用户身份登录时,我得到以下响应...

      {
        "status": true,"message": "User logged in successfully.","data": {
          "user": {
            "id": 60,"first_name": "Ankit","last_name": "Joshi","mobile_no": "9876543211"
          },"access_token": "eyJLCJhbGciOiJIUzI1NR..","token_type": "bearer","ask_for_mobile_no": 0
        }
     }
    
  • 使用以下方法将响应转换为模型类(我已经使用Alamofire进行了api调用

      let decoder = JSONDecoder()
      do {
    
          let responseData = try decoder.decode(ServerResponse <TempuserResponse>.self,from: serverData)
          success(responseData as AnyObject)
      } catch {
          print("Error = \(error)")
          do {
              let responseData = try decoder.decode(ServerResponse<AppUserResponse>.self,from: serverData)
              success(responseData as AnyObject)
          } catch {
              failure(error.localizedDescription as AnyObject)
          }
      }
    

当我运行这段代码时,它总是会尝试将响应转换为ServerResponse<TempuserResponse>,即使响应的类型为ServerResponse<AppUserResponse>

那么我如何通过将其转换为受尊重的模型类来管理这两个响应?

解决方法

由于您的所有属性都是可选的,并使用TempUserResponse函数进行解码,因此首次尝试(decodeIfPresent<T>(_:forKey:)类型)解码JSON响应总是会成功。

因此,JSONDecoder假设您的根对象中键data的值是一个TempUserResponse实例,但所有属性均设置为nil。 (JSON中没有属性键)

为了避免这种行为,您可以在TempUserResponse中设置一个对您有意义的属性,例如tempUser

struct TempUserResponse : Codable {
    let askForMobileNo: Int?
    let provider: String?
    let providerId: String?
    let tempUser: TempUser

    enum CodingKeys: String,CodingKey {
        case askForMobileNo = "ask_for_mobile_no"
        case provider = "provider"
        case providerId = "provider_id"
        case tempUser = "temp_user"
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        askForMobileNo = try values.decodeIfPresent(Int.self,forKey: .askForMobileNo)
        provider = try values.decodeIfPresent(String.self,forKey: .provider)
        providerId = try values.decodeIfPresent(String.self,forKey: .providerId)
        tempUser = try values.decode(TempUser.self,forKey: .tempUser)
    }
}

这样,如果JSON中存在tempUser密钥,则解码将成功,否则,解码将失败,并退回到AppUserResponse解码。

更新:另一种解决方案是将两个结构合并为一个,其所有属性均为可选。

这样,您的模型将简单得多:

struct UserResponse: Codable  {
    let accessToken: String?
    let askForMobileNo: Int?
    let tokenType: String?
    let user: AppUser?
    
    let provider: String?
    let providerId: String?
    let tempUser: TempUser?

    enum CodingKeys: String,CodingKey {
        case accessToken = "access_token"
        case askForMobileNo = "ask_for_mobile_no"
        case tokenType = "token_type"
        case user = "user"
        
        case provider = "provider"
        case providerId = "provider_id"
        case tempUser = "temp_user"
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        accessToken = try values.decodeIfPresent(String.self,forKey: .accessToken)
        askForMobileNo = try values.decodeIfPresent(Int.self,forKey: .askForMobileNo)
        tokenType = try values.decodeIfPresent(String.self,forKey: .tokenType)
        user = try values.decodeIfPresent(AppUser.self,forKey: .user)
        
        provider = try values.decodeIfPresent(String.self,forKey: .providerId)
        tempUser = try values.decodeIfPresent(TempUser.self,forKey: .tempUser)
    }
}

注意:不包括未更改的结构。

解码代码如下:

let decoder = JSONDecoder()
do {
    let responseData = try decoder.decode(ServerResponse<UserResponse>.self,from: Data(jsonString.utf8))
    print(responseData)
} catch {
    print(error)
}

,您只需检查usertempUser属性是否存在即可确定您的情况。

,

您可以有一个额外的图层类,例如

struct FailableResponse <T:Codable,E:Codable> : Codable {
    
    var success:T?
    var failure:E?

    public init(from decoder:Decoder) throws {
      
        let singleValue = try decoder.singleValueContainer()
        success = try singleValue.decode(T.self)
        
        failure = try singleValue.decode(E.self)
        
    }
}

并将正确答案和错误答案传递为TE

注意:未经xcode测试,但可以正常工作

,

替换TempUserResponse模型类

struct TempUserResponse : Codable {

let askForMobileNo : Int?
let provider : String?
let providerId : String?
let tempUser : TempUser?
var appUser: AppUser? = nil

enum CodingKeys: String,CodingKey {
        case askForMobileNo = "ask_for_mobile_no"
        case provider = "provider"
        case providerId = "provider_id"
        case tempUser = "temp_user"
    case appUser = "user"
}

init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)
    askForMobileNo = try values.decodeIfPresent(Int.self,forKey: .askForMobileNo)
    provider = try values.decodeIfPresent(String.self,forKey: .provider)
    providerId = try values.decodeIfPresent(String.self,forKey: .providerId)
    tempUser = try values.decodeIfPresent(TempUser.self,forKey: .tempUser)
    if tempUser == nil{
        appUser = try values.decodeIfPresent(AppUser.self,forKey: .appUser)
    }
}

}

模型类中的响应

do {
                
                let responseData = try decoder.decode(ServerResponse <TempUserResponse>.self,from: serverData)
                print("Response Success: \(responseData)")
                if let tempUser = responseData.data?.tempUser{
                    print("TempUser: \(tempUser)")
                }
                
                if let appuser = responseData.data?.appUser{
                    print("AppUser : \(appuser)")
                }
                
            } catch {
                print("Error = \(error)")
            }