3
\$\begingroup\$

Suppose You have two kind of response like one for success and one for failure

Success response model will looks like

struct UserLogin: Codable {
    let status: Bool?
    let accessToken: String?
    let data: [UserLoginData]?
   ....
}

Failure Model will looks like

struct FailedResponse: Codable {
    let status: Bool?
    let error: ErrorResponse?

}

How I handle this two responses

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 How I use FailableResponse

  APIClient.login(userName: self.loginViewModel.userName, password: self.loginViewModel.password) { (response:FailableResponse<UserLogin,FailedResponse>? , error) in

   }


// METHOD OF API CLIENT 
// API CALLING 
static func login<T:Codable>(userName:String,password:String,completion:@escaping completionResponse<T>) {
        self.performRequest(request: APIRouterUserModule.login(email: userName, password: password)) {(model) in

            self.handleResponseCallCompletion(result: model, completion: completion)
        }
  }
 // Parsing 
 private static func handleResponseCallCompletion<T:Codable>(result:Result<Any>,completion:@escaping completionResponse<T>) {
        let object = CodableHelper<T>().decode(json: result)
        completion(object.object,object.error)
    }

I think It can be more better

Any suggestion Please :)

\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

There are two approaches

  1. For the sake of completeness, I’ll describe the typical, simple solution:

    struct UserLoginResponse: Codable {
        let status: Bool
        let error: ErrorResponse?
        let accessToken: String?
        let data: [UserLoginData]?
    }
    

    The status is not optional (because it’s presumably there regardless). But you can just decode this struct:

    do {
        let responseObject = try decoder.decode(UserLoginResponse.self, from: data)
        switch responseObject.status {
        case true:
            guard let accessToken = responseObject.accessToken, let userLoginData = responseObject.data else {
                throw ParsingError.requiredFieldMissing
            }
    
            // use accessToken and userLoginData here
    
        case false:
            guard let errorObject = responseObject.error else {
                throw ParsingError.requiredFieldMissing
            }
    
            // use errorObject here
        }
    } catch {
        print(error)
    }
    
  2. If you really want to do this generic, wrapper approach, I’d suggest a slight refinement. Notably, the code in your question returns an object for which both the success and error objects are optionals. (There seem like there are a lot of ? thrown in there quite liberally, whereas most API dictate “if success, x and y will be present, if failure, z will be present”.)

    I’d suggest, instead, a pattern that captures the fact that the response will be either success (when status is True) or failure (when status is False), and rather than returning both success and failure objects as optionals, use the Result<Success, Failure> enumeration with associated values, which is included in Swift 5. Or, if you’re using an earlier version of Swift, you can define it yourself:

    enum Result<Success, Failure> {
        case success(Success)
        case failure(Failure)
    }
    

    Then, the API response wrapper can have a non-optional result property of type Result<T, E>, where:

    • If status is True, parse the success object as a non-optional associated value which is the Success type;
    • If status is False, parse the error object as a non-optional associated value which is the Failure type;
       

    Thus:

    struct ApiResponse<T: Codable, E: Codable>: Codable {
        let status: Bool
        var result: Result<T, E>
        var error: E?
    
        enum CodingKeys: String, CodingKey {
            case status, error
        }
    
        init(from decoder: Decoder) throws {
            let values = try decoder.container(keyedBy: CodingKeys.self)
            status = try values.decode(Bool.self, forKey: .status)
            if status {
                let singleValue = try decoder.singleValueContainer()
                result = try .success(singleValue.decode(T.self))
            } else {
                let parsedError = try .failure(values.decode(E.self, forKey: .error))
                error = parsedError
                result = .failure(parsedError)
            }
        }
    }
    

    Then you can do:

    do {
        let responseObject = try decoder.decode(ApiResponse<UserLoginResponse, ErrorResponse>.self, from: data)
        switch responseObject.result {
        case .success(let object):
            print(object.accessToken, object.data)
    
        case .failure(let error):
            print(error)
        }
    } catch {
        print(error)
    }
    

    This would seem to better capture the true nature of the response, that it’s either successful (and you get the non-optional success object) or it’s a failure (and you get the non-optional ErrorResponse object).

    By the way, I’d suggest that in this scenario, that UserLoginResponse be updated to reflect which fields are truly optional and which aren’t. For example, if you know that if status is True, that both accessToken and data will be present, then I’d make those non-optional properties:

    struct UserLoginResponse: Codable {
        let accessToken: String
        let data: [UserLoginData]
    }
    
\$\endgroup\$
1
  • 1
    \$\begingroup\$ No one does it better than you :) , Thank for great explanation; \$\endgroup\$ Commented Apr 29, 2019 at 4:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.