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