JSON Swift4 keep receiving Catch Error

Now wishing to rely on any third party libraries I shifted gear up to Swift 4 from v3 as passing JSON was specifically addressed. However, just trying it out with the OpenWeatherApp API found the catch error would always display?!

struct WeatherMain : Codable {

let humidity : Int?
let pressure : Int?
let temp : Float?
let tempMax : Float?
let tempMin : Float?

enum CodingKeys: String, CodingKey {
    case humidity = "humidity"
    case pressure = "pressure"
    case temp = "temp"
    case tempMax = "temp_max"
    case tempMin = "temp_min"
}

init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)
    humidity = try values.decodeIfPresent(Int.self, forKey: .humidity)
    pressure = try values.decodeIfPresent(Int.self, forKey: .pressure)
    temp = try values.decodeIfPresent(Float.self, forKey: .temp)
    tempMax = try values.decodeIfPresent(Float.self, forKey: .tempMax)
    tempMin = try values.decodeIfPresent(Float.self, forKey: .tempMin)
}

}

class ViewController: UIViewController {

var weather = [WeatherMain]()

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    
    let jsonURL = "http://samples.openweathermap.org/data/2.5/weather?q=London,uk&appid=b6907d289e10d714a6e88b30761fae22"
    let url = URL(string: jsonURL)
    
    URLSession.shared.dataTask(with: url!) { (data, response, error) in
        do {
            self.weather = try JSONDecoder().decode([WeatherMain].self, from: data!)
            
            for weatherCondition in self.weather {
                print(weatherCondition.humidity!)
            }
        }
        catch {
            print("Error")
        }
    }.resume()
}

The JSON response does not contain an array of WeatherMain, it contains a response dictionary with the weather info array stored under a weather key. This works for me:

struct Weather: Codable {

    let humidity: Int?
    let pressure: Int?
    let temp: Float?
    let tempMax: Float?
    let tempMin: Float?

    enum CodingKeys: String, CodingKey {
        case humidity
        case pressure
        case temp
        case tempMax = "temp_max"
        case tempMin = "temp_min"
    }
}

struct Response: Codable {
    let weather: [Weather]
}

let dataURL = "http://samples.openweathermap.org/data/2.5/weather?q=London,uk&appid=b6907d289e10d714a6e88b30761fae22"
do {
    let data = try Data(contentsOf: URL(string: dataURL)!)
    let weather = try JSONDecoder().decode(Response.self, from: data)
    print(weather)
} catch {
    print(error)
}

Many thank for your prompt reply, this is a very much cleaner approach.

To try an understand what you meant, was I cycling the Struct around erroneously expecting data from the url?

I don’t understand the question, but I’ll try to explain the source of your error better. You are trying to decode an array of WeatherMain:

try JSONDecoder().decode([WeatherMain].self, from: data!)

This means the JSON should look something like this:

[
  {
    "temp": 280.32,
    "pressure": 1012,
    "humidity": 81,
    "temp_min": 279.15,
    "temp_max": 281.15
  }
]

But it doesn’t. In fact it doesn’t even look the way I have written the model – it looks like what you want is stored as a dictionary under the main key:

struct Response: Codable {
    let main: Weather
}

let response = try JSONDecoder().decode(Response.self, from: data)
print(response.main) // Weather(humidity: Optional(81), pressure: Optional(1012), temp: Optional(280.320007), tempMax: Optional(281.149994), tempMin: Optional(279.149994))

I just spent the day with Ray Wenderlich learning about JSON and now the syntax makes a lot more sense.

It is true that the data I was after was under the "main" key in the dictionary, and it looks like you access that through the Response struct with the constant "main"?

I think, with the reference before as "weather" and not "main", the data would not match up when retrieved?