Не удается разобрать ответ JSON в SwiftUI

Я пытаюсь получить информацию о погоде через 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 не позволяет показать больше, чем это

Вы получаете ошибку? У вас есть несколько операторов печати, так что же будет напечатано?

Joakim Danielson 21.04.2023 12:43

добавьте: print("--> data: \(String(data: data, encoding: .utf8))"), непосредственно перед let post = try! ... и покажите нам, что он печатает

workingdog support Ukraine 21.04.2023 12:46

Я действительно получаю данные json в String: a String? "{\"now\":1682074908,\"now_dt\":\"2023-04-21T11:01:48.140952‌​Z\",\"info\":{\"n\":‌​true, \"geoid\":98820‌​,\"ulat=55\\u0026lon‌​=37\",\"широта\":55,\"l‌​on\":37,\"tzinfo\":{‌​ \"name\":\"Europe/Mo‌​scow\",sure_mm\":744‌​,\"def_pressure_pa\"‌​:991,\"slug\":\"9882‌​0... Но вылетает с фатальной ошибкой: "попробуй!" выражение неожиданно вызвало ошибку: Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "nowDt", intValue: nil) at "let post = try! JSONDecoder().decode(Post.self, from: data!)"

perfect05 21.04.2023 13:06

Может быть, это как-то связано с тем, что при преобразовании полей словаря JSON в структуру у меня есть имена в верблюжьем регистре, такие как «feelsLike», а не JSON feel_like?

perfect05 21.04.2023 13:08

Да, в этом проблема, просто установите jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase на свой JSONDecoder.

olejnjak 21.04.2023 13:09

Вы можете ясно видеть, что данные json, которые вы получаете с сервера, не соответствуют вашей структуре Post. Например, в данных нет nowDt, есть now_dt и т. д. Структура Post должна точно соответствовать данным, для этого можно использовать enum CodingKeys. Скопируйте все полученные данные в app.quicktype.io, он сгенерирует для вас структуры.

workingdog support Ukraine 21.04.2023 13:18

@workingdogsupportUkraine это было именно то, что я сделал, но мне все равно пришлось вручную исправлять многие поля

perfect05 21.04.2023 13:34

покажите нам точно и полностью, что вы получаете от сервера (печать(...)) и я (или кто-то другой) покажу вам, какие модели вам нужны.

workingdog support Ukraine 21.04.2023 15:02
...manually correct the fields, что означает, что вы удалили важную enum CodingKeys часть, как показано в вашем вопросе.
workingdog support Ukraine 21.04.2023 15:13

из вашего последнего редактирования ошибка указывает, что вам нужно иметь let f, h: Bool? в info. Просто покажите нам, что вы получаете от сервера.

workingdog support Ukraine 21.04.2023 15:22

Скопируйте все свои данные json в app.quicktype.io, чтобы создать модели для вас. Затем замените JSONNull на String? и измените сгенерированный код, чтобы включить Identifiable, где это необходимо. Важно прочитать документацию ответа сервера, чтобы определить, какие свойства являются необязательными. В этом случае добавьте к ним ?. Это то, что я сделал в своем тесте, используя ваши фрагменты данных json, которые вы показываете, и я могу легко декодировать данные в Post. Вы также можете продолжать создавать структуры модели вручную (как вы показываете) и получать ошибки.

workingdog support Ukraine 22.04.2023 01:01
Как сделать HTTP-запрос в Javascript?
Как сделать HTTP-запрос в Javascript?
В JavaScript вы можете сделать HTTP-запрос, используя объект XMLHttpRequest или более новый API fetch. Вот пример для обоих методов:
3
11
118
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я бы порекомендовал сделать его асинхронным. С этим вы можете лучше отлаживать. (См. ниже). Похоже, вы не устанавливаете ключ 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.

Вот несколько советов по отладке, которые вы можете использовать, чтобы понять, что что-то идет не так:

  1. Добавьте точку останова на строку, где вы пытаетесь декодировать данные.
  2. Вы можете распечатать данные в удобочитаемом виде в консоли Xcode с помощью print String(data: data, encoding: .utf8)
  3. Вы можете получить информацию о процессе декодирования, запустив print JSONDecoder().decode(Post.self, from: data!) в консоли Xcode.

Другие вопросы по теме