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