Mahendra
Mahendra

Reputation: 8924

Convert dynamic server response in to model class in swift

The following is the server response handling parent structure...

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

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 Structure

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

}

PROBLEM

When I run this code it always goes and try to convert response into ServerResponse<TempUserResponse> even if the response is of type ServerResponse<AppUserResponse>.

So how can I manage both of the response by converting it into respected model class?

Upvotes: 1

Views: 425

Answers (3)

gcharita
gcharita

Reputation: 8347

The decoding of your JSON response always succeeds in the first try (with TempUserResponse type) because all your properties are optional and decoded using decodeIfPresent<T>(_:forKey:) function.

So, JSONDecoder assumes that the value of the key data in your root object is a TempUserResponse instance but with all of is properties set to nil. (none of the property keys are present in the JSON)

In order to avoid that behavior you can set a property that make sense to you, to be mandatory in TempUserResponse, like for example 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)
    }
}

That way the decoding will succeed if the tempUser key is present in the JSON and will fail when there is not and fall back to the AppUserResponse decoding.

Update: Another solution would be to merge both structs into one with all of its properties as optionals.

That way your model will be a lot simpler:

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

Note: Structs that not changed are not included.

The decoding code will look like this:

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

and you can just check if user or tempUser property exist to determine your case.

Upvotes: 2

Dhawal
Dhawal

Reputation: 1065

Replace TempUserResponse model 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)
    }
}

}

Response in model 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)")
            }

Upvotes: 0

Prashant Tukadiya
Prashant Tukadiya

Reputation: 16466

You can have one extra layer class like

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

And Pass Your correct response and wrong response as T and E

Note: Not tested in xcode but should work

Upvotes: 0

Related Questions