I have a model called College
class College : Decodable {
let name : String
let id : String
let iconUrl : String
}
And a few college related APIs, each with a slightly different response. 2 examples are
GET api/v1/colleges response JSON for this API is
{ "success": String, "colleges": [College] }
GET api/v1/college/{collegeID} response JSON for this API is
{ "success": String, "college": College }
Now, from both the responses I need to get only the college information, the "success" key is not useful to me. My question is, how to get the college information without creating separate response models for each API? Currently I have implemented separate classes for each API response
class GetCollegesResponse : Decodable {
let success : String
let colleges : [College]
}
class GetCollegeResponse : Decodable {
let success : String
let college : College
}
And I use them in respective API calls like so
Alamofire.request(api/v1/colleges ....).responseJSON { response in
let resp = JSONDecoder().decode(GetCollegesResponse.self, response.data)
//get colleges from resp.colleges
}
Alamofire.request(api/v1/college/\(id) ....).responseJSON { response in
let resp = JSONDecoder().decode(GetCollegeResponse.self, response.data)
// get college form resp.college
}
Is there a simpler way to get this done?
Probably the right approach is to model the response as a generic type, like something like this:
struct APIResponse<T: Decodable> {
let success: String
let payload: T
}
from which you could extract the payload.
The problem is that the key with the payload changes: it's college
for a single result and colleges
for multiple college results.
If you truly don't care and just want the payload, we could effectively ignore it and decode any key (other than "success") as an expected type T
:
struct APIResponse<T: Decodable> {
let success: String
let payload: T
// represents any string key
struct ResponseKey: CodingKey {
var stringValue: String
var intValue: Int? = nil
init(stringValue: String) { self.stringValue = stringValue }
init?(intValue: Int) { return nil }
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: ResponseKey.self)
let sKey = container.allKeys.first(where: { $0.stringValue == "success" })
let pKey = container.allKeys.first(where: { $0.stringValue != "success" })
guard let success = sKey, let payload = pKey else {
throw DecodingError.keyNotFound(
ResponseKey(stringValue: "success|any"),
DecodingError.Context(
codingPath: container.codingPath,
debugDescription: "Expected success and any other key"))
}
self.success = try container.decode(String.self, forKey: success)
self.payload = try container.decode(T.self, forKey: payload)
}
}
Then you could decode based on the expected payload:
let resp = try JSONDecoder().decode(APIResponse<[College]>.self, response.data)
let colleges = resp.payload