Swift4, JSON, keyNotFound, No value associated with key - json

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

Four issues:
Add let results : Results in the SunPosition struct.
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.
Wrong type to decode: JSONDecoder().decode(SunPosition.self, from: data) rather than JSONDecoder().decode(SunPosition.Results.self, from: data).
To get the results you have to print(sunPosition.results).
Three notes:
Delete the entire extension. In this case you get the initializer for free.
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 ...
I recommend to handle the status, too. Add this in the SunPosition struct
let status : String
var success : Bool { return status == "OK" }

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

Related

Translate a json dictionary object to Array of objects

I am getting back JSON that looks like this:
{
"success":true,
"timestamp":1650287883,
"base":"EUR",
"date":"2022-04-18",
"rates":{
"USD":1.080065,
"EUR":1,
"JPY":136.717309,
"GBP":0.828707,
"AUD":1.465437,
"CAD":1.363857
}
}
I was expecting rates to be an array, but it's an object. The currency codes may vary. Is there a way to Decode this with Swift's built-in tools?
I'm certain this won't work:
struct ExchangeRateResponse: Decodable {
let success: Bool
let base: String
let date: Date
let rates: [[String: Double]]
private enum ResponseKey: String, CodingKey {
case success, base, date, rates
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: ResponseKey.self)
success = try container.decode(Bool.self, forKey: .success)
base = try container.decode(String.self, forKey: .base)
date = try container.decode(Date.self, forKey: .date)
rates = try container.decode([[String: Double]].self, forKey: .rates)
}
}
your model is wrong. you can do this:
struct YourModelName {
let success: Bool
let timestamp: Int
let base, date: String
let rates: [String: Double]
}
after that you can try do decode it.
something like this:
do {
let jsonDecoder = JSONDecoder()
let loadData = try jsonDecoder.decode(YourModelName.self, from: data!)
// 'loadData' is your data that you want. for your problem you have to use 'loadData.rates'. Hint: you have to use it in 'for' loop!
DispatchQueue.main.async { _ in
// if you have to update your UI
}
} catch {
print(error)
}
First of all you cannot decode date to Date out of the box, but you can decode timestamp to Date.
Second of all it's impossible to decode a dictionary to an array. This is like apples and oranges.
But fortunately you can map the dictionary to an array because it behaves like an array (of tuples) when being mapped.
Just create an other struct Rate
struct Rate {
let code: String
let value: Double
}
struct ExchangeRateResponse: Decodable {
let success: Bool
let timestamp: Date
let base: String
let date: String
let rates: [Rate]
private enum CodingKeys: String, CodingKey {
case success, base, timestamp, date, rates
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
success = try container.decode(Bool.self, forKey: .success)
timestamp = try container.decode(Date.self, forKey: .timestamp)
base = try container.decode(String.self, forKey: .base)
date = try container.decode(String.self, forKey: .date)
let rateData = try container.decode([String: Double].self, forKey: .rates)
rates = rateData.map(Rate.init)
}
}
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .secondsSince1970
let result = try decoder.decode(ExchangeRateResponse.self, from: data)
print(result)
} catch {
print(error)
}
Or still shorter if you map the dictionary after decoding the stuff
struct ExchangeRateResponse: Decodable {
let success: Bool
let timestamp: Date
let base: String
let date: String
let rates: [String:Double]
}
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .secondsSince1970
let result = try decoder.decode(ExchangeRateResponse.self, from: data)
let rates = result.rates.map(Rate.init)
print(rates)
} catch {
print(error)
}

Parsing Json from Web API

Hello my name is Nico,
I am a complete beginner in App programming/SwiftUI.
I am trying to parse json data from an web api but somehow I cannot parse the data correctly. I assume that my json structure is not correct but I cannot find the problem.
The Json which I get from the Web API looks something like this:
{
"pois": [
{
"id": "2635094451",
"lat": "52.410150",
"lat_s": "52.4",
"lng": "10.776630",
"lng_s": "10.8",
"street": "Röntgenstraße",
"content": "8137285512",
"backend": "0-239283152",
"type": "1",
"vmax": "50",
"counter": "0",
"create_date": "2021-11-18 13:21:50",
"confirm_date": "2021-11-18 13:21:43",
"gps_status": "-",
"info": " {\"qltyCountryRoad\":1,\"confirmed\":\"0\",\"gesperrt\":\"0\",\"precheck\":\"[Q1|21|0]\"}",
"polyline": ""
}
],
"grid": []
}
My Structure looks like this:
struct APIResponse: Codable {
let pois: [InputDataPois]
let grid: [InputDataGrid]
private enum CodingKeys: String, CodingKey {
case pois
case grid
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.pois = try container.decode(APIResponse.self, forKey: .pois).pois
self.grid = try container.decode(APIResponse.self, forKey: .grid).grid
}
}
struct InputDataPois: Codable, Identifiable {
let id:String
let lat:String
let lat_s:String
let lng:String
let lng_s:String
let street:String
let content:String
let backend:String
let type:String
let vmax:String
let counter:String
let create_date:String
let confirm_date:String
let gps_status:String
let info:String
let polyline:String
}
extension InputDataPois {
private enum CodingKeys: String, CodingKey {
case id
case lat
case lat_s
case lng
case lng_s
case street
case content
case backend
case type
case vmax
case counter
case create_date
case confirm_date
case gps_status
case info
case polyline
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(String.self, forKey: .id)
self.lat = try container.decode(String.self, forKey: .lat)
self.lat_s = try container.decode(String.self, forKey: .lat_s)
self.lng = try container.decode(String.self, forKey: .lng)
self.lng_s = try container.decode(String.self, forKey: .lng_s)
self.street = try container.decode(String.self, forKey: .street)
self.content = try container.decode(String.self, forKey: .content)
self.backend = try container.decode(String.self, forKey: .backend)
self.type = try container.decode(String.self, forKey: .type)
self.vmax = try container.decode(String.self, forKey: .vmax)
self.counter = try container.decode(String.self, forKey: .counter)
self.create_date = try container.decode(String.self, forKey: .create_date)
self.confirm_date = try container.decode(String.self, forKey: .confirm_date)
self.gps_status = try container.decode(String.self, forKey: .gps_status)
self.info = try container.decode(String.self, forKey: .info)
self.polyline = try container.decode(String.self, forKey: .polyline)
}
}
struct InputDataGrid: Codable {
}
and my Bundle like this:
extension Bundle {
func decode(_ file: String) -> [InputDataPois] {
// 1. Locate the Json File
guard let url = URL(string: file) else {
fatalError("Failed to locate \(file) in bundle")
}
// 2.Create a property for the Data
guard let data = try? Data(contentsOf: url) else {
fatalError("Failed to Load \(file) from bundle.")
}
// 3. Create a property for the data
let str = String(decoding: data, as: UTF8.self)
print("\(str)")
guard let loaded = try? JSONDecoder().decode(APIResponse.self, from: data).pois else {
fatalError("Failed to decode \(file) from bundle.")
}
// 4. Return the ready-to-use data
return loaded
}
}
And In my View I am using:
let speed: [InputDataPois] = Bundle.main.decode("https://cdn2.atudo.net/api/1.0/vl.php?type=0,1,2,3,4,5,6&box=52.36176390234046,10.588760375976562,52.466468685912744,11.159706115722656")
The error I am getting looks something like this.
ErrorMessage
ConsoleError
Thanks in advance for you help.
Remove your custom Codable implementation and coding keys. They are not necessary, the compiler can generate them for you with this simple JSON. Then everything should work.
The problem here specifically is your APIResponse decoding:
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.pois = try container.decode(APIResponse.self, forKey: .pois).pois
self.grid = try container.decode(APIResponse.self, forKey: .grid).grid
}
To decode an APIResponse you try to decode two APIResponses. This again would try to decode APIResponse leading to endless recursion. The only reason you are not seeing that is that your JSON contains arrays making the second call to container(keyedBy:) fail. Try setting a breakpoint (or if you must a print statement) on the first line of your init(from:) and you will see that this is called a second time before it fails.
To fix that you would need to decode the actual type of your properties:
self.pois = try container.decode([InputDataPois].self, forKey: .pois)
But as I said, there is nothing in your JSON format requiring manually implementing the decoder, so the best is to let the compiler synthesize it for you.

Expected to decode Dictionary<String, Any> but found an array instead with Nested Containers

So, I am trying to parse this JSON using the Codable protocols:
https://randomuser.me/api/?results=100
That are basically 100 random users.
Here's my User class initializer from decoder, that I need because the User is an entity in a Core Data Model:
required convenience public init(from decoder: Decoder) throws {
let managedObjectContext = CoreDataStack.sharedInstance.persistentContainer.viewContext
guard let entity = NSEntityDescription.entity(forEntityName: "User", in: managedObjectContext) else {
fatalError("Failed to decode User")
}
self.init(entity: entity, insertInto: managedObjectContext)
let container = try decoder.container(keyedBy: CodingKeys.self)
let results = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .results)
let name = try results.nestedContainer(keyedBy: CodingKeys.self, forKey: .name)
firstName = try name.decode(String.self, forKey: .firstName)
lastName = try name.decode(String.self, forKey: .lastName)
let location = try results.nestedContainer(keyedBy: CodingKeys.self, forKey: .location)
let street = try location.decode(String.self, forKey: .street)
let city = try location.decode(String.self, forKey: .city)
let postcode = try location.decode(String.self, forKey: .postcode)
address = street + ", " + city + ", " + postcode
email = try results.decode(String.self, forKey: .email)
let pictures = try results.nestedContainer(keyedBy: CodingKeys.self, forKey: .pictures)
pictureURL = try pictures.decode(String.self, forKey: .pictureURL)
}
This is the defective line:
let results = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .results)
Here's the complete error:
typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil))
I think it's due to the structure of the JSON, that is an array of 100 elements under the key "results", and I think the problem could be in doing them all together.
How should I handle this?
The error is clear: The value for results is an array and nestedContainers expects a dictionary.
To decode the user array you need an umbrella struct outside of the Core Data classes.
struct Root : Decodable {
let results: [User]
}
While decoding Root the init method in User is called for each array item.
To use nestedContainers you have to separate the CodingKeys.
This is the init method without the Core Data stuff. postcode can be String or Int
private enum CodingKeys: String, CodingKey { case name, location, email, picture }
private enum NameCodingKeys: String, CodingKey { case first, last }
private enum LocationCodingKeys: String, CodingKey { case street, city, postcode }
private enum PictureCodingKeys: String, CodingKey { case large, medium, thumbnail }
required convenience public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let nameContainer = try container.nestedContainer(keyedBy: NameCodingKeys.self, forKey: .name)
let firstName = try nameContainer.decode(String.self, forKey: .first)
let lastName = try nameContainer.decode(String.self, forKey: .last)
let locationContainer = try container.nestedContainer(keyedBy: LocationCodingKeys.self, forKey: .location)
let street = try locationContainer.decode(String.self, forKey: .street)
let city = try locationContainer.decode(String.self, forKey: .city)
let postcode : String
do {
let postcodeInt = try locationContainer.decode(Int.self, forKey: .postcode)
postcode = String(postcodeInt)
} catch DecodingError.typeMismatch {
postcode = try locationContainer.decode(String.self, forKey: .postcode)
}
let address = street + ", " + city + ", " + postcode
let email = try container.decode(String.self, forKey: .email)
let pictureContainer = try container.nestedContainer(keyedBy: PictureCodingKeys.self, forKey: .picture)
let pictureURL = try pictureContainer.decode(URL.self, forKey: .large)
}
This is a very simplified version but it handles your Json data correctly
struct Result : Codable {
let results: [User]
}
struct User: Codable {
let gender: String
let name: Name
}
struct Name: Codable {
let title: String
let first: String
let last: String
}
let decoder = JSONDecoder()
let data = jsonString.data(using: .utf8) //Replace with url download
do {
let json = try decoder.decode(Result.self, from: data!)
} catch {
print(error)
}

Parsing json blob field in Swift

I am reading JSON from a web service in swift which is in the following format
[{
"id":1,
"shopName":"test",
"shopBranch":"main",
"shopAddress":"usa",
"shopNumber":"5555555",
"logo":[-1,-40,-1,-32],
"shopPath":"test"
},
{
"id":2,
"shopName":"test",
"shopBranch":"main",
"shopAddress":"usa",
"shopNumber":"66666666",
"logo":[-1,-50,-2,-2],
"shopPath":"test"
}]
I have managed to read all the strings easily but when it comes to the logo part I am not sure what should I do about it, this is a blob field in a mySQL database which represent an image that I want to retrieve in my swift UI, here is my code for doing that but i keep getting errors and not able to figure the right way to do it:
struct Brand: Decodable {
private enum CodingKeys: String, CodingKey {
case id = "id"
case name = "shopName"
case branch = "shopBranch"
case address = "shopAddress"
case phone = "shopNumber"
case logo = "logo"
case path = "shopPath"
}
let id: Int
let name: String
let branch: String
let address: String
let phone: String
let logo: [String]
let path: String
}
func getBrandsJson() {
let url = URL(string: "http://10.211.55.4:8080/exam/Test")
URLSession.shared.dataTask(with: url!, completionHandler: {(data, response, error) in
guard let data = data, error == nil else {
print(error!);
return
}
print(response.debugDescription)
let decoder = JSONDecoder()
let classes = try! decoder.decode([Brand].self, from: data)
for myClasses in classes {
print(myClasses.branch)
if let imageData:Data = myClasses.logo.data(using:String.Encoding.utf8){
let image = UIImage(data:imageData,scale:1.0)
var imageView : UIImageView!
}
}
}).resume()
}
Can someone explain how to do that the right way I have searched a lot but no luck
First of all, you should better tell the server side engineers of the web service, that using array of numbers is not efficient to return a binary data in JSON and that they should use Base-64 or something like that.
If they are stubborn enough to ignore your suggestion, you should better decode it as Data in your custom decoding initializer.
struct Brand: Decodable {
private enum CodingKeys: String, CodingKey {
case id = "id"
case name = "shopName"
case branch = "shopBranch"
case address = "shopAddress"
case phone = "shopNumber"
case logo = "logo"
case path = "shopPath"
}
let id: Int
let name: String
let branch: String
let address: String
let phone: String
let logo: Data
let path: String
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(Int.self, forKey: CodingKeys.id)
self.name = try container.decode(String.self, forKey: CodingKeys.name)
self.branch = try container.decode(String.self, forKey: CodingKeys.branch)
self.address = try container.decode(String.self, forKey: CodingKeys.address)
self.phone = try container.decode(String.self, forKey: CodingKeys.phone)
self.path = try container.decode(String.self, forKey: CodingKeys.path)
//Decode the JSON array of numbers as `[Int8]`
let bytes = try container.decode([Int8].self, forKey: CodingKeys.logo)
//Convert the result into `Data`
self.logo = Data(bytes: bytes.lazy.map{UInt8(bitPattern: $0)})
}
}
And you can write the data decoding part of your getBrandsJson() as:
let decoder = JSONDecoder()
do {
//You should never use `try!` when working with data returned by server
//Generally, you should not ignore errors or invalid inputs silently
let brands = try decoder.decode([Brand].self, from: data)
for brand in brands {
print(brand)
//Use brand.logo, which is a `Data`
if let image = UIImage(data: brand.logo, scale: 1.0) {
print(image)
//...
} else {
print("invalid binary data as an image")
}
}
} catch {
print(error)
}
I wrote some lines to decode array of numbers as Data by guess. So if you find my code not working with your actual data, please tell me with enough description and some examples of actual data. (At least, you need to show me a first few hundreds of the elements in the actual "logo" array.)
Replace
let logo: [String]
with
let logo: [Int]
to get errors use
do {
let classes = try JSONDecoder().decode([Brand].self, from: data)
}
catch {
print(error)
}

Swift Decoding nested JSON

I have a problem with parsing data from NBP api "http://api.nbp.pl/api/exchangerates/tables/a/?format=json" . I created struct CurrencyDataStore and Currency
struct CurrencyDataStore: Codable {
var table: String
var no : String
var rates: [Currency]
enum CodingKeys: String, CodingKey {
case table
case no
case rates
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
table = ((try values.decodeIfPresent(String.self, forKey: .table)))!
no = (try values.decodeIfPresent(String.self, forKey: .no))!
rates = (try values.decodeIfPresent([Currency].self, forKey: .rates))!
} }
struct Currency: Codable {
var currency: String
var code: String
var mid: Double
enum CodingKeys: String, CodingKey {
case currency
case code
case mid
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
currency = try values.decode(String.self, forKey: .currency)
code = try values.decode(String.self, forKey: .code)
mid = try values.decode(Double.self, forKey: .mid)
}
}
In controllerView class I wrote 2 methods to parse data from API
func getLatestRates(){
guard let currencyUrl = URL(string: nbpApiUrl) else {
return
}
let request = URLRequest(url: currencyUrl)
let task = URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) -> Void in
if let error = error {
print(error)
return
}
if let data = data {
self.currencies = self.parseJsonData(data: data)
}
})
task.resume()
}
func parseJsonData(data: Data) -> [Currency] {
let decoder = JSONDecoder()
do{
let currencies = try decoder.decode([String:CurrencyDataStore].self, from: data)
}
catch {
print(error)
}
return currencies
}
This code didn't work. I have this error "typeMismatch(Swift.Dictionary, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary but found an array instead.", underlyingError: nil))".
Could you help me?
The JSON being returned by that API gives you an array, not a dictionary, but you're telling the JSONDecoder to expect a dictionary type. Change that line to:
let currencies = try decoder.decode([CurrencyDataStore].self, from: data)