Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
509 views
in Technique[技术] by (71.8m points)

swift - json file is missing/ struct is wrong

I have been trying to get this code to work for like 6 hours. I get the error: "failed to convert The data couldn’t be read because it is missing." I don't know while the File is missing is there something wrong in my models(structs). Do I need to write a struct for very json dictionary? Currently I have only made those JSON dictionaries to a struct, which I actually need. The full JSON file can be found at https://api.met.no/weatherapi/sunrise/2.0/.json?lat=40.7127&lon=-74.0059&date=2020-12-22&offset=-05:00 . I want to be able to print the time of the sunrise, sunset and solar noon as well as the elevation of the sun at solar noon. It's currently 1 am and I am desperate. Good Night!

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.7127&lon=-74.0059&date=2020-12-22&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
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You don't really have a SwiftUI class, but that is a different question. I am going to work on fixing getData(). I have tried to comment it extensively, but let me know if you have any questions.

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

With regard to your structs, you only need them for the data you are trying to parse. If you don't need it, don't worry about it. I would make this a separate class named MetDecoder or something that makes sense to you and indicates the decoder for your JSON. You will also note that I changed the names of some of the variables. You can do that so long as you use a CodingKeys enum to translate your JSON to your struct as in the case of dailyData = "location", etc. This is ugly JSON, and I am not sure why the Met decided everything should be a string, but this decoder is tested and it works:

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?
}

You should see what the National Weather Service does to us in the US to get the JSON. Lastly, when working on JSON I find the following pages VERY helpful: JSON Formatter & Validator which will help you parse out the wall of text that gets returned in a browser, and quicktype which will parse JSON into a programming language like Swift. I will warn you that the parsing can give some very ugly structs in Swift, but it gives you a nice start. I used both sites for this answer.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

1.4m articles

1.4m replys

5 comments

56.8k users

...