将动态服务器响应转换为 swift 中的模型 class

Convert dynamic server response in to model class in swift

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

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: .provider)
        providerId = 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)
    }

}

问题

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

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

你可以多一层class喜欢

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 模型class

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)
    }
}

}

模型中的响应 class

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)")
            }

您的 JSON 响应的解码总是在第一次尝试时成功(使用 TempUserResponse 类型),因为您的所有属性都是可选的并使用 decodeIfPresent<T>(_:forKey:) 函数解码。

因此,JSONDecoder 假定根对象中键 data 的值是一个 TempUserResponse 实例,但所有 is 属性都设置为 nil。 (none 的 属性 键出现在 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)
    }
}

这样,如果 tempUser 密钥存在于 JSON 中,解码将成功,如果不存在,解码将失败,并回退到 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: .provider)
        providerId = 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 属性 是否存在来确定你的情况。