Я пытаюсь получить информацию о погоде через API, запрос работает отлично, я могу получить данные JSON. Но по какой-то причине URLSession пропускается и, следовательно, оставляет нулевое значение в переменной post для хранения данных. Попытка отобразить информацию:
var body: some View{
TabView {
WeatherView()
.tabItem {
Image(systemName: "cloud.rain")
.font(.largeTitle)
Text("Weather")
}
}
}
struct WeatherView : View {
@State var post: Post?
var body: some View{
return VStack {
Text("lol")
}
.onAppear(){
WeatherApi().getPost{ (post) in
self.post = post
}
}
//return VStack{
// Text("Unable to retrieve data from API Server")
//}.onAppear()
}
}
class WeatherApi {
func getPost(completion: @escaping (Post) -> ()){
guard let url = URL(string: "https://api.weather.yandex.ru/v2/forecast?lat=50&lon=37") else {return}
print("kek")
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("[key here]", forHTTPHeaderField: "X-Yandex-API-Key")
URLSession.shared.dataTask(with: request){(data, _, _) in
let post = try! JSONDecoder().decode(Post.self, from: data!)
print(post)
DispatchQueue.main.async {
completion(post)
}
}
.resume()
}
}
struct Post : Codable, Identifiable {
let id = UUID()
let now: Int
let now_dt: String
let info: info
let geo_object: geo_object
let yesterday: yesterday
let fact: fact
let forecasts: [forecast]
}
// MARK: - fact
struct fact : Codable, Identifiable {
let id = UUID()
let obs_time, uptime, temp, feels_like: Int
let icon: icon
let condition: condition
let cloudness, prec_type, prec_prob, prec_strength: Int
let is_thunder: Bool
let wind_speed: Double
let wind_dir: wind_dir
let pressure_mm, pressure_pa, humidity: Int
let daytime: daytime
let polar: Bool
let season, source: String
let accum_prec: [String: Double]
let soil_moisture: Double
let soil_temp, uv_index, wind_gust: Int
}
enum condition: String, Codable {
case clear
case cloudy
case light_rain
case overcast
case partly_cloudy
}
enum daytime:String, Codable {
case d
case n
}
enum icon: String, Codable {
case bkn_d
case bkn_n
case ovc
case ovc_ra
case skc_d
case skc_n
}
enum wind_dir: String, Codable {
case n
case ne
case s
case sw
case w
}
// MARK: - Forecast
struct forecast: Codable, Identifiable {
let id = UUID()
let date: String
let date_ts, week: Int
let sunrise, sunset, rise_begin, set_end: String
let moon_code: Int
let moon_text: String
let parts: parts
let hours: [hour]
let biomet: biomet?
}
// MARK: - biomet
struct biomet: Codable, Identifiable {
let id = UUID()
let index: Int
let condition: String
}
// MARK: - Hour
struct hour: Codable, Identifiable {
let id = UUID()
let hour: String
let hour_ts, temp, feels_like: Int
let icon: icon
let condition: condition
let cloudness: Double
let prec_type, prec_strength: Int
let is_thunder: Bool
let wind_dir: wind_dir
let wind_speed, wind_gust: Double
let pressure_mm, pressure_pa, humidity, uv_index: Int
let soil_temp: Int
let soil_moisture: Double
let prec_mm, prec_period, prec_prob: Int
}
// MARK: - Parts
struct parts: Codable, Identifiable {
let id = UUID()
let morning, night, day, day_short: day
let night_short, evening: day
}
// MARK: - Day
struct day: Codable, Identifiable {
let id = UUID()
let source: String
let temp_min, temp_avg, temp_max: Int?
let wind_speed, wind_gust: Double
let wind_dir: String
let pressure_mm, pressure_pa, humidity, soil_temp: Int
let soil_moisture, prec_mm: Double
let prec_prob, prec_period: Int
let cloudness: Double
let prec_type: Int
let prec_strength: Double
let icon: icon
let condition: condition
let uv_index: Int?
let feels_like: Int
let daytime: daytime
let polar: Bool
let fresh_snow_mm: Int
let temp: Int?
}
// MARK: - gej_object
struct geo_object: Codable, Identifiable {
let id = UUID()
let district:String
let locality, province, country: country
}
// MARK: - country
struct country: Codable, Identifiable {
let id: Int
let name: String
}
// MARK: - info
struct info: Codable, Identifiable {
let id = UUID()
let n: Bool
let geoid: Int
let url: String
let lat, lon: Int
let tzinfo: tzinfo
let def_pressure_mm, def_pressure_pa: Int
let slug: String
let zoom: Int
let nr, ns, nsr, p: Bool
let f, h: Bool
}
// MARK: - Tzinfo
struct tzinfo: Codable, Identifiable {
let id = UUID()
let name, abbr: String
let dst: Bool
let offset: Int
}
// MARK: - yesterday
struct yesterday: Codable, Identifiable {
let id = UUID()
let temp: Int
}
Мне удалось выяснить, что это URLSession.shared.dataTask, который не анализирует данные JSON в объектах, но я не уверен, что делать дальше. UPD: Было несколько проблем с верблюжьим регистром, например, windGust вместо wind_gust в JSON. Я изменил все поля, которые могли вызвать эту проблему, но теперь я получаю еще одну ошибку: цепочка 5: Фатальная ошибка: 'попробуй!' выражение неожиданно вызвало ошибку: Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "h", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "info", intValue: nil)], debugDescription: "Нет значения, связанного с ключом CodingKeys(stringValue: "h", intValue: nil) ("h").", baseError: nil)) Измененные структуры прилагаются UPD2: Ответ сервера следующий: String? "{"now":1682087274,"now_dt":"2023-04-21T14:27:54.524102Z","info":{"n":true,"geoid":20741,"url":"https:/ /yandex.ru/pogoda/20741?lat=10\u0026lon=10","lat":10,"lon":10,"tzinfo":{"name":"Африка/Лагос","abbr":" WAT","dst":false,"offset":3600},"def_pressure_mm":713,"def_pressure_pa":950,"slug":"20741","zoom":10,"nr":true,"ns ":true,"nsr":true,"p":false,"f":true,"h":false},"geo_object":{"район":null,"местность":null,"провинция": {"id":20741,"name":"Баучи"},"country":{"id":20741,"name":"Нигерия"}},"вчера":{"temp":37}," fact":{"obs_time":1682085600,"uptime":1682087274,"temp":38,"feels_like":38,"icon":"ovc-ra","condition":"свет-дождь","облачность" ":1,"prec_type":1,"prec_prob":90,"prec_strength":0,25,"is_thunder":false,"wind_speed":3,9,"wind_dir":"nw","pressure_mm":708,"pressure_pa ":943,"влажность":32,"дневное время":"d","полярный":false,"сезон":"весна","источник":"станция","soil_moisture":0,11,"soil_temp": 42,"uv_index":2,"wind_gust":6.6},"прогнозы":[{"дата":"2023-04-21","date_ts":1682031600,"неделя":16,"восход":" 06:0" Xcode не позволяет показать больше, чем это
добавьте: print("--> data: \(String(data: data, encoding: .utf8))"), непосредственно перед let post = try! ... и покажите нам, что он печатает
Я действительно получаю данные json в String: a String? "{\"now\":1682074908,\"now_dt\":\"2023-04-21T11:01:48.140952Z\",\"info\":{\"n\":true, \"geoid\":98820,\"ulat=55\\u0026lon=37\",\"широта\":55,\"lon\":37,\"tzinfo\":{ \"name\":\"Europe/Moscow\",sure_mm\":744,\"def_pressure_pa\":991,\"slug\":\"98820... Но вылетает с фатальной ошибкой: "попробуй!" выражение неожиданно вызвало ошибку: Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "nowDt", intValue: nil) at "let post = try! JSONDecoder().decode(Post.self, from: data!)"
Может быть, это как-то связано с тем, что при преобразовании полей словаря JSON в структуру у меня есть имена в верблюжьем регистре, такие как «feelsLike», а не JSON feel_like?
Да, в этом проблема, просто установите jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase на свой JSONDecoder.
Вы можете ясно видеть, что данные json, которые вы получаете с сервера, не соответствуют вашей структуре Post. Например, в данных нет nowDt, есть now_dt и т. д. Структура Post должна точно соответствовать данным, для этого можно использовать enum CodingKeys. Скопируйте все полученные данные в app.quicktype.io, он сгенерирует для вас структуры.
@workingdogsupportUkraine это было именно то, что я сделал, но мне все равно пришлось вручную исправлять многие поля
покажите нам точно и полностью, что вы получаете от сервера (печать(...)) и я (или кто-то другой) покажу вам, какие модели вам нужны.
...manually correct the fields, что означает, что вы удалили важную enum CodingKeys часть, как показано в вашем вопросе.
из вашего последнего редактирования ошибка указывает, что вам нужно иметь let f, h: Bool? в info. Просто покажите нам, что вы получаете от сервера.
Скопируйте все свои данные json в app.quicktype.io, чтобы создать модели для вас. Затем замените JSONNull на String? и измените сгенерированный код, чтобы включить Identifiable, где это необходимо. Важно прочитать документацию ответа сервера, чтобы определить, какие свойства являются необязательными. В этом случае добавьте к ним ?. Это то, что я сделал в своем тесте, используя ваши фрагменты данных json, которые вы показываете, и я могу легко декодировать данные в Post. Вы также можете продолжать создавать структуры модели вручную (как вы показываете) и получать ошибки.

Я бы порекомендовал сделать его асинхронным. С этим вы можете лучше отлаживать. (См. ниже). Похоже, вы не устанавливаете ключ API в URL-адресе. Проверьте документацию, как это сделать.
struct WeatherView : View {
@State var post: Post?
var body: some View{
return VStack {
Text("lol")
}
.task {
do {
try await WeatherApi().getPost()
} catch {
print(error)
}
}
//return VStack{
// Text("Unable to retrieve data from API Server")
//}.onAppear()
}
}
class WeatherApi {
func getPost() async throws -> Post? {
guard let url = URL(string: "https://api.weather.yandex.ru/v2/forecast?lat=50&lon=37") else {
return nil
}
let (data, _) = try await URLSession.shared.data(from: url)
let result = try JSONDecoder().decode(Post.self, from: data)
print(result)
return result
}
}
Как уже упоминалось в комментариях к вашему исходному сообщению, похоже, проблема в том, что вы пытаетесь сопоставить значения с несовпадающими ключами.
Если серверная часть отвечает ключами змеиного регистра (snake_case), но ваше приложение ожидает верблюжий регистр (camelCase), вы можете либо настроить JSONDecoder на использование convertFromSnakeCase как есть KeyDecodingStrategy, либо настроить CodingKeys.
Вот несколько советов по отладке, которые вы можете использовать, чтобы понять, что что-то идет не так:
print String(data: data, encoding: .utf8)print JSONDecoder().decode(Post.self, from: data!) в консоли Xcode.
Вы получаете ошибку? У вас есть несколько операторов печати, так что же будет напечатано?