When I hit my configuration API with Postman I am given the following json response back. In this response the two apiVersion keys are numbers and not strings.
{
"data": {
"availability": {
"auth": true,
"ab": true,
"cd": true
},
"helloWorldConfiguration": {
"apiKey": "abcefg",
"rootUrl": "https://foo",
"apiVersion": 3
},
"fooBarConfiguration": {
"baseUrl": "https://foo",
"apiVersion": 1,
"privateApiPath": "",
"publicApiPath": "dev",
"tokenPath": ""
}
},
"errors": []
}
When I try to decode it it fails with a typeMismatch
error. When I output the contents of the response, I see the following which looks fine to me.
data = {
availability = {
auth = 1;
ab = 1;
cd = 1;
};
helloWorldConfiguration = {
apiVersion = 1;
baseUrl = "https://foo";
privateApiPath = "";
publicApiPath = dev;
tokenPath = "";
};
fooBarConfiguration = {
apiKey = abcefg;
apiVersion = 3;
rootUrl = "https://foo";
};
};
errors = (
);
The error given to me indicates that data.helloWorldConfiguration.apiVersion
is of type string instead of int. We can see from the original HTTP response I get from Postman that's not the case.
typeMismatch(Swift.Int, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "data", intValue: nil), CodingKeys(stringValue: "helloWorldConfiguration", intValue: nil), CodingKeys(stringValue: "apiVersion", intValue: nil)], debugDescription: "Expected to decode Int but found a string/data instead.", underlyingError: nil)) 21:17:40 ERROR Unable to decode the response data into a model representation.
My model represents those properties as integers so it would appear that it receives the response and considers those numbers to be strings, which they're not.
public struct ServerConfiguration: Decodable {
let availability: AvailabilityConfiguration
let helloWorldConfiguration: HelloWorldConfiguration
let fooBarConfiguration: FooBarConfiguration
init(availability: AvailabilityConfiguration, helloWorldConfiguration: HelloWorldConfiguration, fooBarConfiguration: FloatSinkConfiguration) {
self.availability = availability
self.helloWorldConfiguration = helloWorldConfiguration
self.fooBarConfiguration = fooBarConfiguration
}
}
public struct FooBarConfiguration: Decodable {
let baseUrl: String
let apiVersion: Int
let privateApiPath: String
let publicApiPath: String
let tokenPath: String
init(baseUrl: String, apiVersion: Int, privateApiPath: String, publicApiPath: String, tokenPath: String) {
self.baseUrl = baseUrl
self.apiVersion = apiVersion
self.privateApiPath = privateApiPath
self.publicApiPath = publicApiPath
self.tokenPath = tokenPath
}
}
public struct AvailabilityConfiguration: Decodable {
let auth: Bool
let ab: Bool
let cd: Bool
init(auth: Bool, ab: Bool, cd: Bool) {
self.auth = auth
self.ab = ab
self.cd = cd
}
}
public struct HelloWorldConfiguration: Codable {
let apiKey: String
let rootUrl: String
let apiVersion: Int
init(apiKey: String, rootUrl: String, apiVersion: Int) {
self.apiKey = apiKey
self.rootUrl = rootUrl
self.apiVersion = apiVersion
}
}
As you can see my apiVersion
members are both of type integer along with the json response. What am I doing wrong here? I assume what's happening is Swift is considering the numbers in the json string, regardless of how they're actually represented in the json. Is that the case?
Edit to show utf8 string of Alamofire response data
21:44:06 INFO GET: https:foo/configuration
{
"data" : {
"availability" : {
"auth" : true,
"ab" : true,
"cb" : true
},
"helloWorldConfiguration" : {
"apiKey" : "abcd",
"rootUrl" : "https://foo",
"apiVersion" : "3"
},
"fooBarConfiguration" : {
"baseUrl" : "https://foo",
"apiVersion" : "1",
"privateApiPath" : "",
"publicApiPath" : "dev",
"tokenPath" : "auth/token"
}
},
"errors" : []
}
It would seem that despite the API correctly returning apiVersion
as a number, Swift is turning it into a string. Am I decoding it incorrectly?
func getRoute<TResponseData: Decodable>(route:String, completion: @escaping (TResponseData) -> Void) throws {
let headers = try! self.getHeaders(contentType: ContentType.json)
let completeUrl: String = self.getUrl(route: route, requestUrl: nil)
logger.info("GET: \(completeUrl)")
Alamofire.request(
completeUrl,
method: .get,
parameters: nil,
encoding: JSONEncoding.default,
headers: headers)
.validate()
.responseJSON { (response) -> Void in
self.logger.info("GET Response: \(String(describing:response.response?.statusCode))")
switch response.result {
case .success(_):
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom(Date.toFooBarDate)
do {
let result = try decoder.decode(TResponseData.self, from: response.data!)
completion(result)
} catch DecodingError.dataCorrupted(let error) {
self.logger.error(error.underlyingError!)
return
} catch {
print(response.result.value!)
print(error)
self.logger.error("Unable to decode the response data into a model representation.")
return
}
}
}
print
this. The collection type output is ambiguous, because you cannot distinguishString
andInt
. And you can delete all initializers in the structs. You get them for free. – vadianapiversion
is not andInt
. Itsstring
. Change it toString
in yourstruct
– AmitapiVersion
is clearlyString
. Everything in double quotes isString
. So the decoder error message is correct (actually it's always correct 😉 ). – vadiandata
property having numbers decoded as strings. As mentioned earlier theirresponse.result.value
prints it correct though. Perhaps I just convert it to json string before decoding it. – Johnathon Sullinger