Kch
Kch

Reputation: 69

How to deal with responses from an API which may contain an Array or Dictionary?

I have an API call which checks for the UserLogin and Password and sends a response based on whether the username and password Pairs are correct or not.

The API returns a dictionary if the username-password pair is incorrect and an array of dictionaries if the username-password is correct.

The problem I face is that I am unable to downcast the response from Alamofire to a particular data type.

   func afLoginDriver(firstName: String,password:String,completion: @escaping ([Dictionary<String, Any>])->Void){
    let driverLoginURL = "URL here.."
    let parameters = ["firstName" : firstName, "Password" : password]
    AF.request(driverLoginURL, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: nil).responseJSON { response in

        switch response.result {
            case .success:
                let driverLoginResponse = response.value as? [Dictionary<String, Any>]
                completion(driverLoginResponse)
            break
        case .failure(let error):
            print(error)
        }
    }
}

The variable driverLoginResponse throws up an error if the username-password pairs is incorrect as it is only a dictionary and not an array of dictionary.

I tried using the guard let statement but still was not able to get this to work.

Upvotes: 1

Views: 946

Answers (2)

Mojtaba Hosseini
Mojtaba Hosseini

Reputation: 119224

I would use Codable and enum for that but consider this:

First, implement an enum with any possible response:

enum LoginDriverResponseValue {
    case dictionary([String: Any])
    case array([[String: Any]])
    case string(String)
    case unknown(Any)
}

then, change the function signature to adapt with that enum:

func afLoginDriver(firstName: String, password: String, completion: @escaping (LoginDriverResponseValue)->Void) {
,,,
}

And lastly, switch on the enum and call corresponding completion:

guard let responseValue = response.value else { return }

switch responseValue {
case let result as [String: Any]: completion(.dictionary(result))
case let result as [[String: Any]]: completion(.array(result))
case let result as String: completion(.string(result))
default: completion(.unknown(responseValue))
}

- More encapsulated way:

you can encapsulate responseValue type detection into the enum LoginDriverResponseValue:

extension LoginDriverResponseValue {
    init(responseValue: Any) {
        switch responseValue {
        case let result as [String: Any]: self = .dictionary(result)
        case let result as [[String: Any]]: self = .array(result)
        case let result as String: self = .string(result)
        default: self = .unknown(responseValue)
        }
    }
}

So then the only thing you need in the function will be:

guard let responseValue = response.value else { return }
completion(LoginDriverResponseValue(responseValue: responseValue))

Upvotes: 2

Vladyslav Shmatok
Vladyslav Shmatok

Reputation: 434

  1. Cast the response values to the potential Types, and handle each:
if let responseOne = response.value as? [String: Any] {
    // ...
} else if let responseTwo = response.value as? [[String: Any]] {
    // ...
}

  1. You could use Codable:
struct Response: Codable {
   var valueOne: [String: Any]?
   var valueTwo: [[String: Any]]?
}

Upvotes: 3

Related Questions