Wazza
Wazza

Reputation: 1885

Decoding Json not working, not giving error either

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

Answers (1)

David Pasztor
David Pasztor

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

Related Questions