Reputation: 1885
Im trying to decode the following json:
[
{
"breeds":[
{
"weight":{
"imperial":"65 - 75",
"metric":"29 - 34"
},
"height":{
"imperial":"21 - 28",
"metric":"53 - 71"
},
"id":14,
"name":"American Foxhound",
"country_code":"US",
"bred_for":"Fox hunting, scent hound",
"breed_group":"Hound",
"life_span":"8 - 15 years",
"temperament":"Kind, Sweet-Tempered, Loyal, Independent, Intelligent, Loving"
}
],
"id":"p4xvDeEpW",
"url":"https://cdn2.thedogapi.com/images/p4xvDeEpW.jpg",
"width":680,
"height":453
}
]
I have my Models set as:
struct Request: Decodable {
let response: [Response]
}
struct Response: Decodable {
let breeds: [Dog]
let url: String
}
struct Dog: Decodable {
let weight: Measurement
let height: Measurement
let name: String
let countryCode: String?
let breedGroup: String
let lifeSpan: String
let temperament: String
let origin: String?
}
struct Measurement: Decodable {
let imperial: String
let metric: String
}
I call the API as follows:
func fetchDogs(with request: URLRequest, completion: @escaping (([Dog]?, String?, Error?) -> ())) {
URLSession.shared.dataTask(with: request) { (data, response, error) in
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
guard let data = data,
let request = try? decoder.decode(Request.self, from: data) else {
completion(nil, nil, error)
return
}
completion(request.response[0].breeds, request.response[0].url, nil)
}.resume()
}
But it always hits the guard completion(nil, nil, error)
line and I'm not sure why I'm not sure how to pull apart the first few lines of the JSON to start off with. Using:
struct Request: Decodable {
let response: [Response]
}
doesn't seem right. If I update the request to decode the Response
struct instead it still hits the guard.
My request builder looks like:
struct RequestBuilder {
private let baseURLString: String
static var defaultBuilder: RequestBuilder {
return RequestBuilder(
baseURLString: Constant.URL.dogsBase
)
}
func dogsRequest() -> URLRequest {
return dogRequest(path: Constant.Path.search)
}
private func dogRequest(path: String) -> URLRequest {
guard var components = URLComponents(string: baseURLString) else {
preconditionFailure(Constant.PreconditionFailure.baseURL)
}
components.path = path
guard let url = components.url else {
preconditionFailure(Constant.PreconditionFailure.baseURL)
}
var request = URLRequest(url: url)
request.setValue(Constant.Key.dogs, forHTTPHeaderField: "x-api-key")
return request
}
}
The api can be found here
Upvotes: 1
Views: 637
Reputation: 54706
The issue is that when you pass Request.self
to decode.decode
, JSONDecoder
expects to find a Dictionary
whose key is response
due to your declaration of Request
.
You simply need to pass [Response].self
to the decode
method.
guard let data = data,
let request = try? decoder.decode([Response].self, from: data) else {
completion(nil, nil, error)
return
}
If you didn't mask the decoding error using try?
, you'd see the issue immediately from the error message:
typeMismatch(Swift.Dictionary, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary but found an array instead.", underlyingError: nil))
You should always use a normal try
statement in a do-catch
block when you are unsure about whether your code will work or not, since this way you can actually see the error message. JSONDecoder
throws pretty useful errors, so I'd suggest you never mask them into a nil
using try?
.
Upvotes: 3