4

I'm playing around with Swift 4 Codable again and just when I think I have the hang of it, I get this issue with decoding the Weather key from the response.

 let jsonData = """
    {"coord":{"lon":-113,"lat":35},
"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01n"}],
"base":"stations",
"main":{"temp":291.15,"pressure":1022,"humidity":72,"temp_min":291.15,"temp_max":291.15},
"visibility":16093,"wind":{"speed":3.6,"deg":200},
"clouds":{"all":1},
"dt":1503294780,"sys":{"type":1,"id":321,"message":0.184,"country":"US","sunrise":1503320222,"sunset":1503367937},
"id":5308281,"name":"Paulden","cod":200}
"""

Swift Models:

//Top Level Container
struct Response: Codable {

    enum ResponseKeys: String, CodingKey {
        case name
        case code = "cod"
        case main
        case weather
    }
    //Nested Level Keys: Response > Weather
    enum WeatherKeys: String, CodingKey {
        case weather
    }
    //Nested Level Keys: Response > Main
    enum MainKeys: String, CodingKey {
        case temp
        case pressure
        case humidity
        case tempMin = "temp_min"
        case tempMax = "temp_max"
    }
    //Properties
    var name: String
    var code: Int
    var temp: Double
    var pressure: Int
    var weather:[WeatherObj]

    //Custom Init
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: ResponseKeys.self)
        let weatherContainer = try container.nestedContainer(keyedBy: WeatherKeys.self, forKey: .weather)
        let mainContainer = try container.nestedContainer(keyedBy: MainKeys.self, forKey: .main)
        self.name = try container.decode(String.self, forKey: .name)
        self.code = try container.decode(Int.self, forKey: .code)
        self.pressure = try mainContainer.decode(Int.self, forKey: .pressure)
        self.temp = try mainContainer.decode(Double.self, forKey: .temp)

        // Here is where it throws: the data couldn’t be read because it isn’t in the correct format
        self.weather = try weatherContainer.decode([WeatherObj].self, forKey: .weather)
    }
}

I'm not sure what I'm not doing correctly. Everything else decodes into my model perfectly. It's only when I try to decode an Array. Any suggestions?

1 Answer 1

6

You decoded the weather key incorrectly. It's a top-level key so you don't need to create a subcontainer. This is enough:

self.weather = try container.decode([WeatherObj].self, forKey: .weather)

However I'd actually recommend that you create a private struct that stays very close to the JSON to ease the decoding process. Then you can pick off the pieces you want to initialize the data model:

struct Response: Codable {
    var name: String
    var code: Int
    var temp: Double
    var pressure: Int
    var weather: [WeatherObj]

    // This stays as close to the JSON as possible to minimize the amount of manual code
    // It uses snake_case and the JSON's spelling of "cod" for "code". Since it's private,
    // outside caller can never access it
    private struct RawResponse: Codable {
        var name: String
        var cod: Int
        var main: Main
        var weather: [WeatherObj]

        struct Main: Codable {
            var temp: Double
            var pressure: Int
        }
    }

    init(from decoder: Decoder) throws {
        let rawResponse = try RawResponse(from: decoder)

        // Now pick the pieces you want
        self.name     = rawResponse.name
        self.code     = rawResponse.cod
        self.temp     = rawResponse.main.temp
        self.pressure = rawResponse.main.pressure
        self.weather  = rawResponse.weather
    }
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.