Kirill Grinev
Kirill Grinev

Reputation: 31

Swift4, JSON, keyNotFound, No value associated with key

I need to do Sunset Sunrise App, and this is my code. But I have this error:

Error serializing json: keyNotFound(Sunrise_Sunset.SunPosition.Results.(CodingKeys in _B4291256871B16D8D013EC8806040532).sunrise, Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key sunrise (\"sunrise\").", underlyingError: nil))

And I don't understand how to fix it. Maybe someone had this problem. I will be grateful for any help) This is the API, which I have: https://sunrise-sunset.org/api

struct SunPosition: Codable {
struct Results: Codable {
    let sunrise: String
    let sunset: String
    let solarNoon: String
    let dayLenght: String
    let civilTwilightBegin: String
    let civilTwilightEnd: String
    let nauticalTwilightBegin: String
    let nauticalTwilightEnd: String
    let astronomicalTwilightBegin: String
    let astronomicalTwilightEnd: String

    enum CodingKey:String, Swift.CodingKey {
        case sunrise = "sunrise"
        case sunset = "sunset"
        case solarNoon = "solar_noon"
        case dayLenght = "day_length"
        case civilTwilightBegin = "civil_twilight_begin"
        case civilTwilightEnd = "civil_twilight_end"
        case nauticalTwilightBegin = "nautical_twilight_begin"
        case nauticalTwilightEnd = "nautical_twilight_end"
        case astronomicalTwilightBegin = "astronomical_twilight_begin"
        case astronomicalTwilightEnd = "astronomical_twilight_end"
    }
  }
}

extension SunPosition.Results {
init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)
    sunrise = try values.decode(String.self, forKey: .sunrise)
    sunset = try values.decode(String.self, forKey: .sunset)
    solarNoon = try values.decode(String.self, forKey: .solarNoon)
    dayLenght = try values.decode(String.self, forKey: .dayLenght)
    civilTwilightBegin = try values.decode(String.self, forKey: .civilTwilightBegin)
    civilTwilightEnd = try values.decode(String.self, forKey: .civilTwilightEnd)
    nauticalTwilightBegin = try values.decode(String.self, forKey: .nauticalTwilightBegin)
    nauticalTwilightEnd = try values.decode(String.self, forKey: .nauticalTwilightEnd)
    astronomicalTwilightBegin = try values.decode(String.self, forKey: .astronomicalTwilightBegin)
    astronomicalTwilightEnd = try values.decode(String.self, forKey: .astronomicalTwilightEnd)
}
}

class ViewController: UIViewController {

override func viewDidLoad() {
    super.viewDidLoad()

    let jsonUrlString = "https://api.sunrise-sunset.org/json?lat=36.7201600&lng=-4.4203400"
    guard let url = URL(string: jsonUrlString) else { return }

    URLSession.shared.dataTask(with: url) { (data, response, error) in

        guard let data = data else { return }

        do {
            let sunPosition = try JSONDecoder().decode(SunPosition.Results.self, from: data)
            print(sunPosition)
        }catch let jsonErr {
            print("Error serializing json:", jsonErr)
        }

    }.resume()
}
}

Upvotes: 3

Views: 9314

Answers (2)

ScottyBlades
ScottyBlades

Reputation: 14063

To rule it out, if for some reason your endpoint is returning an error message or nil data, you'll get this error.

Upvotes: 1

vadian
vadian

Reputation: 285220

Four issues:

  1. Add let results : Results in the SunPosition struct.
  2. Typo: private enum CodingKeys: String, CodingKey rather than enum CodingKey :String, Swift.CodingKey, note the singular / plural difference, the private attribute is recommended but does not cause the issue.
  3. Wrong type to decode: JSONDecoder().decode(SunPosition.self, from: data) rather than JSONDecoder().decode(SunPosition.Results.self, from: data).
  4. To get the results you have to print(sunPosition.results).

Three notes:

  1. Delete the entire extension. In this case you get the initializer for free.
  2. Add &formatted=0 to the URL and set the dateDecodingStrategy of the decoder to .iso8601 to get Date objects. Change the type of all date related properties from String to Date and the type of dayLenght from String to TimeInterval. To change dateDecodingStrategy write

    let decoder = JSONDecoder()
    decoder.dateDecodingStrategy = .iso8601
    let sunPosition = try decoder.decode(SunPosition.self ...
    
  3. I recommend to handle the status, too. Add this in the SunPosition struct

    let status : String
    
    var success : Bool { return status == "OK" }
    

Upvotes: 8

Related Questions