Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

iOS

Gene Bogdanovich
Gene Bogdanovich
14,618 Points

Decoding JSON arrays with nested containers

Hello there. I'm having a little bit of trouble trying to figure out how to decode this JSON into a single type using nested containers. My goal is to end up with fully initialized ForecastWeather type without creating any intermediate types to match JSON response structure. Is there any way to do that? I know my code would work perfectly if "data" key wouldn't return array, but just a single dictionary. Having array there, crashes my code. Is there a way to inform container that there would be an array? Because I really want to have just a single type and decode directly because I think it's much cleaner. Thanks!

JSON:

let json = """
{
"latitude": 53.9,
"longitude": 27.55,
"timezone": "Europe/Minsk",
"daily": {
"data":[
{
"time": 1566594000,
"summary": "Mostly cloudy throughout the day.",
"icon": "partly-cloudy-day",
"sunriseTime": 1566615904,
"sunsetTime": 1566667347,
"moonPhase": 0.79,
"precipIntensity": 0.0001,
"precipIntensityMax": 0.0004,
"precipIntensityMaxTime": 1566658800,
"precipProbability": 0.03,
"precipType": "rain",
"temperatureHigh": 75.37,
"temperatureHighError": 6.8,
"temperatureHighTime": 1566655200,
"temperatureLow": 55.96,
"temperatureLowError": 6.77,
"temperatureLowTime": 1566702000,
"apparentTemperatureHigh": 75.37,
"apparentTemperatureHighTime": 1566655200,
"apparentTemperatureLow": 55.96,
"apparentTemperatureLowTime": 1566702000,
"dewPoint": 52.41,
"humidity": 0.69,
"pressure": 1027.98,
"windSpeed": 4.19,
"windGust": 8.4,
"windGustTime": 1566658800,
"windBearing": 277,
"cloudCover": 0.25,
"uvIndex": 5,
"uvIndexTime": 1566640800,
"visibility": 10,
"ozone": 292.3,
"temperatureMin": 51.67,
"temperatureMinError": 6.78,
"temperatureMinTime": 1566615600,
"temperatureMax": 75.37,
"temperatureMaxError": 6.81,
"temperatureMaxTime": 1566655200,
"apparentTemperatureMin": 51.67,
"apparentTemperatureMinTime": 1566615600,
"apparentTemperatureMax": 75.37,
"apparentTemperatureMaxTime": 1566655200
}
]
},
"offset": 3
}
""".data(using: .utf8)!

Code:

struct ForecastWeather {
    let maximumTemperature: Double
    let minimumTemperature: Double
    let icon: String

    enum CodingKeys: String, CodingKey {
        case maximumTemperature = "temperatureHigh"
        case minimumTemperature = "temperatureLow"
        case icon = "icon"
    }

    enum MiddleCodingKeys: String, CodingKey {
        case data
    }

    enum TopLevelCodingKeys: String, CodingKey {
        case daily
    }
}



extension ForecastWeather: Decodable {
    init(from decoder: Decoder) throws {
        let topLevelContainer = try decoder.container(keyedBy: TopLevelCodingKeys.self)
        let middleContainer = try topLevelContainer.nestedContainer(keyedBy: MiddleCodingKeys.self, forKey: .daily)
        let innerContainer = try middleContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .data)
        self.maximumTemperature = try innerContainer.decode(Double.self, forKey: .maximumTemperature)
        self.minimumTemperature = try innerContainer.decode(Double.self, forKey: .minimumTemperature)
        self.icon = try innerContainer.decode(String.self, forKey: .icon)
    }
}

let decoder = JSONDecoder()


let forecast = try decoder.decode(ForecastWeather.self, from: json)
forecast.maximumTemperature
forecast.minimumTemperature
forecast.icon

1 Answer

Gene Bogdanovich
Gene Bogdanovich
14,618 Points

I actually figured this out on my own. So if someone has the same question -- you're covered. The problem here is that there's 4 levels of data depth, so it would require 4 containers that I've called c1, c2, c3, c4. And for the array we would have to use nestedUnkeyedContainer.

let c1 = try decoder.container(keyedBy: TopLevelCodingKeys.self)
let c2 = try c1.nestedContainer(keyedBy: MiddleCodingKeys.self, forKey: .daily)
var c3 = try c2.nestedUnkeyedContainer(forKey: .data)
 let c4 = try c3.nestedContainer(keyedBy: CodingKeys.self)

This code would be useful if you're trying to expand iOS weather app to show forecast for a week in a table view.