Home > other >  OpenWeather One Call API coord not loading
OpenWeather One Call API coord not loading

Time:05-26

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.

CodePudding user response:

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)
    }
}

CodePudding user response:

try this cut-down ResponseBody model:

struct ResponseBody: Identifiable, Codable {
    let id = UUID()
    
    let lat, lon: Double
    let timezone: String
    let timezone_offset: Int
    let current: Current?
}

struct Current: Codable {
    let dt, sunrise, sunset, pressure, humidity, clouds, visibility, windDeg: Int
    let temp, feelsLike, windSpeed: Double
    let windGust: Double?
    let weather: [Weather]
    
    enum CodingKeys: String, CodingKey {
        case dt, sunrise, sunset, temp, pressure, humidity, clouds, visibility, weather
        case feelsLike = "feels_like"
        case windSpeed = "wind_speed"
        case windDeg = "wind_deg"
        case windGust = "wind_gust"
    }
}

struct Weather: Identifiable, Codable {
    let id: Int
    let main, description, icon: String
}

Use it like this:

struct ContentView: View {
    @State var response: ResponseBody?
    let manager = WeatherManager()
    
    var body: some View {
        Text("\(response?.current?.temp ?? 0)")
            .task {
                //  Sydney, Australia
                do {
                    response = await try manager.getCurrentWeather(latitude: -33.861536, longitude: 151.215206)
                    print("\n-----> response: \(response) \n")
                } catch {
                    print("\n-----> error: \(error) \n")
                }
            }
    }
}

For a much better implementation, see my lib OWOneCall, at: https://github.com/workingDog/OWOneCall

  • Related