Search code examples
iosjsonswiftdecodeopenweathermap

OpenWeather One Call API coord not loading


In my Weather app, I call the OpenWeather One Call API. In my Xcode debug console, I get:

Error getting weather: keyNotFound(CodingKeys(stringValue: "coord", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"coord\", intValue: nil) (\"coord\").", underlyingError: nil))

I tested this app on a Simulator and Physical Device. Doesn't work.

Please give me information on how to fix this. Really appreciate this.

Here is the part in my code where it parses the JSON:

class WeatherManager {
    // HTTP request to get the current weather depending on the coordinates we got from LocationManager
    func getCurrentWeather(latitude: CLLocationDegrees, longitude: CLLocationDegrees) async throws -> ResponseBody {
        guard let url = URL(string: "https://api.openweathermap.org/data/2.5/onecall?lat=\(latitude)&lon=\(longitude)&units=metric&appid=") else { fatalError("Missing URL") }


        let urlRequest = URLRequest(url: url)
        
        let (data, response) = try await URLSession.shared.data(for: urlRequest)
        
        guard (response as? HTTPURLResponse)?.statusCode == 200 else { fatalError("Error while fetching data") }
        
        let decodedData = try JSONDecoder().decode(ResponseBody.self, from: data)
        
        return decodedData
    }
}

Here is the first few parts of ResponseBody:

struct ResponseBody: Decodable {
    public let lat, lon: Double
    var weather: [WeatherResponse]
    var current: CurrentResponse
    var name: String
    var wind: WindResponse
    var sun: SunResponse

    struct WeatherResponse: Decodable {
        var id: Double
        var main: String
        var description: String
        var icon: String
    }

Help would be appreciated.


Solution

  • OpenWeatherMap is well documented, please read the docs.

    There are multiple different APIs, I guess your struct represents another API data.

    The basic OneCall root object (omitting minutely, daily, hourly and alerts) is

    struct OneCall: Decodable {
        let lat, lon: Double
        let timezone : String
        let timezoneOffset : Int
        let current: Current
    }
    

    And the descendants Current and Weather are

    struct Current: Decodable {
        let dt, sunrise, sunset : Date
        let temp, feelsLike, dewPoint, uvi, windSpeed : Double
        let pressure, humidity, clouds, visibility, windDeg : Int
        let windGust : Double?
        let weather : [Weather]
    }
    
    struct Weather: Decodable, Identifiable, CustomStringConvertible {
        let id : Int
        let main, description, icon : String
    }
    

    dt, sunrise and sunset are decoded as Date and the snake_case keys are converted to camelCase by applying appropriate decoding strategies.

    I highly recommend to build the URL with URLComponents and URLQueryItems, apiKey is the API key constant.

    let apiKey = "•••••••••"
    
    enum WeatherManagerError : Error { case missingURL, badResponse }
    
    class WeatherManager {
        // HTTP request to get the current weather depending on the coordinates we got from LocationManager
        func getCurrentWeather(latitude: CLLocationDegrees, longitude: CLLocationDegrees) async throws -> OneCall {
    
            var urlComponents = URLComponents(string: "https://api.openweathermap.org/data/2.5/onecall")!
            let queryItems = [URLQueryItem(name: "appid", value: apiKey),
                              URLQueryItem(name: "lat", value: "\(latitude)"),
                              URLQueryItem(name: "lon", value: "\(longitude)"),
                              URLQueryItem(name: "units", value: "metric")]
            urlComponents.queryItems = queryItems
            guard let url = urlComponents.url else { throw WeatherManagerError.missingURL }
            
            let (data, response) = try await URLSession.shared.data(from: url)
            
            guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw WeatherManagerError.badResponse }
            let decoder = JSONDecoder()
            decoder.keyDecodingStrategy = .convertFromSnakeCase
            decoder.dateDecodingStrategy = .secondsSince1970
            return try decoder.decode(OneCall.self, from: data)
        }
    }