файл json отсутствует / структура неправильная

#json #swift #parsing

#json #swift #синтаксический анализ

Вопрос:

Я пытался заставить этот код работать около 6 часов. Я получаю сообщение об ошибке: «не удалось преобразовать, данные не могут быть прочитаны, потому что они отсутствуют». Я не знаю, пока файл отсутствует, что-то не так в моих моделях (структурах). Нужно ли мне писать структуру для очень json-словаря? В настоящее время я создал только те словари JSON для структуры, которые мне действительно нужны. Полный файл JSON можно найти по адресу https://api.met.no/weatherapi/sunrise/2.0/.json?lat=40.7127amp;lon=-74.0059amp;date=2020-12-22amp;offset=-05:00 . Я хочу иметь возможность печатать время восхода, захода солнца и солнечного полудня, а также высоту солнца в солнечный полдень. Сейчас 1 час ночи, и я в отчаянии. Спокойной ночи!

 class ViewController: NSViewController {
    
    @IBOutlet weak var sunriseField: NSTextField!
    @IBOutlet weak var sunsetField: NSTextField!
    @IBOutlet weak var daylengthField: NSTextField!
    
    override func viewDidLoad() {
        super.viewDidLoad()

        
        
        
        let url = "https://api.met.no/weatherapi/sunrise/2.0/.json?lat=40.7127amp;lon=-74.0059amp;date=2020-12-22amp;offset=-05:00"
        getData(from: url)
        

        // Do any additional setup after loading the view.
    }
    
    private func getData(from url: String) {
        
        let task = URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: {data, response, error in
            
            guard let data = data, error == nil else {
                print("something went wrong")
                return
            }
            
            var result: MyTime?
            do {
                result = try JSONDecoder().decode(MyTime.self, from: data)
            }
            catch {
                print("failed to convert (error.localizedDescription)")
            }
            
            guard let json = result else {
                return
            }
            
            
            
            let sunrise1 = json.sunrise.time
            

            
            DispatchQueue.main.async { [weak self] in
                self?.sunriseField.stringValue = sunrise1
            }
            
            print(json)
        
            
            
        })
        
        task.resume()

    }
    

    override var representedObject: Any? {
        didSet {
        // Update the view, if already loaded.
        }
    }

}
 

struct MyData : Codable {
    let location : Location
    let meta : Meta
}
struct MyTime : Codable {
     let solarnoon : Solarnoon
     let sunset : Sunset
     let sunrise : Sunrise
}

struct Location : Codable {
    let height : String
    let time : [MyTime]
    let longitude : String
    let latitude : String
}

struct Meta : Codable {
    let licenseurl : String
}

struct Solarnoon : Codable {
    let desc : String
    let time : String
    let elevation : String
}

struct Sunrise : Codable {
    let desc : String
    let time : String
}

struct Sunset : Codable {
    let time : String
    let desc : String
}
 

Комментарии:

1. Можете ли вы изменить print("failed to convert (error.localizedDescription)") на print("failed to convert (error)") , и скажите нам, что вы получаете?

Ответ №1:

На самом деле у вас нет класса SwiftUI, но это другой вопрос. Я собираюсь поработать над исправлением getData(). Я пытался подробно прокомментировать это, но дайте мне знать, если у вас возникнут какие-либо вопросы.

 private func getData(from url: String) {
    
    // Personally I like converting the string to a URL to unwrap it and make sure it is valid:
    guard let url = URL(string: urlString) else {
        print("Bad URL: (urlString)")
        return
    }

    let config = URLSessionConfiguration.default
    // This will hold the request until you have internet
    config.waitsForConnectivity = true
    
    URLSession.shared.dataTask(with: url) { data, response, error in
        
        // A check for a bad response
        guard let httpResponse = response as? HTTPURLResponse,
              httpResponse.statusCode == 200 else {
            print("Bad Server Response")
            return
        }
        if let data = data {
        // You can print(data) here that will shown you the number of bytes returned for debugging.

            //This work needs to be done on the main thread:
            DispatchQueue.main.async {
                let decoder = JSONDecoder()
                if let json = try? decoder.decode(MetDecoder.self, from: data){
                    print(json)
                    //At this point, you have your data in a struct
                    self.sunriseTime = json.dailyData?.solarData?.first?.sunrise?.time
                }
            }
        }
    }
    .resume()
}
 

Что касается ваших структур, они нужны вам только для данных, которые вы пытаетесь проанализировать. Если вам это не нужно, не беспокойтесь об этом. Я бы сделал это отдельным классом с именем MetDecoder или чем-то, что имеет смысл для вас и указывает декодер для вашего JSON. Вы также заметите, что я изменил имена некоторых переменных. Вы можете сделать это, если используете перечисление CodingKeys для перевода вашего JSON в вашу структуру, как в случае dailyData = "location" , и т.д. Это уродливый JSON, и я не уверен, почему Met решил, что все должно быть строкой, но этот декодер протестирован, и он работает:

 import Foundation

// MARK: - MetDecoder
struct MetDecoder: Codable {
    let dailyData: DailyData?
    
    enum CodingKeys: String, CodingKey {
        case dailyData = "location"
    }

}

// MARK: - Location
struct DailyData: Codable {
    let solarData: [SolarData]?
    
    enum CodingKeys: String, CodingKey {
        case solarData = "time"
    }

}

// MARK: - Time
struct SolarData: Codable {
    let sunrise, sunset: RiseSet?
    let solarnoon: Position?
    let date: String?

    enum CodingKeys: String, CodingKey {
        case sunrise, sunset, solarnoon, date
    }
}

// MARK: - HighMoon
struct Position: Codable {
    let time: String?
    let desc, elevation, azimuth: String?
}

// MARK: - Moonrise
struct RiseSet: Codable {
    let time: String?
    let desc: String?
}
 

Вы должны увидеть, что Национальная метеорологическая служба делает с нами в США, чтобы получить JSON. Наконец, при работе с JSON я нахожу следующие страницы ОЧЕНЬ полезными:
JSON Formatter amp; Validator, который поможет вам разобрать текстовую строку, возвращаемую в браузере, и
quicktype
, который преобразует JSON в язык программирования, такой как Swift. Я предупреждаю вас, что синтаксический анализ может дать некоторые очень уродливые структуры в Swift, но это дает вам хорошее начало. Я использовал оба сайта для этого ответа.

Ответ №2:

Новая платформа Apple, Combine, помогает упростить код, необходимый для асинхронных запросов на выборку. Я использовал MetDecoder в ответе @Yrb выше (вы можете принять его ответ) и изменил функцию getData() . Просто убедитесь, что вы импортируете Combine вверху.

 import Combine

var sunriseTime: String?
var sunsetTime: String?
var solarNoonTime: String?
var solarNoonElevation: String?

func getData() {
    let url = URL(string: "https://api.met.no/weatherapi/sunrise/2.0/.json?lat=40.7127amp;lon=-74.0059amp;date=2020-12-22amp;offset=-05:00")!
    
    URLSession.shared.dataTaskPublisher(for: url)
        // fetch on background thread
        .subscribe(on: DispatchQueue.global(qos: .background))
        // recieve response on main thread
        .receive(on: DispatchQueue.main)
        // ensure there is data
        .tryMap { (data, response) in
            guard
                let httpResponse = response as? HTTPURLResponse,
                httpResponse.statusCode == 200 else {
                throw URLError(.badServerResponse)
            }
            return data
        }
        // decode JSON data to MetDecoder
        .decode(type: MetDecoder.self, decoder: JSONDecoder())
        // Handle results
        .sink { (result) in
            // will return success or failure
            print("completion: (result)")
        } receiveValue: { (value) in
            // if success, will return MetDecoder
            // here you can update your view
            print("value: (value)")
            if let solarData = value.dailyData?.solarData?.first {
                self.sunriseTime = solarData.sunrise?.time
                self.sunsetTime = solarData.sunset?.time
                self.solarNoonTime = solarData.solarnoon?.time
                self.solarNoonElevation = solarData.solarnoon?.elevation
            }
        }
        // After recieving response, the URLSession is no longer needed amp; we can cancel the publisher
        .cancel()
}
 

Комментарии:

1. Этот ответ прекрасен! Я все еще обдумываю Combine…

Ответ №3:

судя по данным json (2-я запись), похоже, вам нужно как минимум:

 struct MyTime : Codable {
 let solarnoon : Solarnoon?
 let sunset : Sunset?
 let sunrise : Sunrise?
}
 

и вам нужно:

         var result: MyData?
        do {
            result = try JSONDecoder().decode(MyData.self, from: data)
        }
        catch {
            print("----> error failed to convert (error)")
        }