Parse JSON from file using Codable swift - json
I have JSON file with cities
[
{"country":"UA","name":"Hurzuf","_id":707860,"coord":{"lon":34.283333,"lat":44.549999}},
{"country":"RU","name":"Novinki","_id":519188,"coord":{"lon":37.666668,"lat":55.683334}},
{"country":"NP","name":"Gorkhā","_id":1283378,"coord":{"lon":84.633331,"lat":28}},
{"country":"IN","name":"State of Haryāna","_id":1270260,"coord":{"lon":76,"lat":29}},
{"country":"UA","name":"Holubynka","_id":708546,"coord":{"lon":33.900002,"lat":44.599998}},
{"country":"NP","name":"Bāgmatī Zone","_id":1283710,"coord":{"lon":85.416664,"lat":28}},
{"country":"RU","name":"Mar’ina Roshcha","_id":529334,"coord":{"lon":37.611111,"lat":55.796391}},
{"country":"IN","name":"Republic of India","_id":1269750,"coord":{"lon":77,"lat":20}},
{"country":"NP","name":"Kathmandu","_id":1283240,"coord":{"lon":85.316666,"lat":27.716667}},
{"country":"UA","name":"Laspi","_id":703363,"coord":{"lon":33.733334,"lat":44.416668}},
{"country":"VE","name":"Merida","_id":3632308,"coord":{"lon":-71.144997,"lat":8.598333}},
{"country":"RU","name":"Vinogradovo","_id":473537,"coord":{"lon":38.545555,"lat":55.423332}},
{"country":"IQ","name":"Qarah Gawl al ‘Ulyā","_id":384848,"coord":{"lon":45.6325,"lat":35.353889}},
{"country":"RU","name":"Cherkizovo","_id":569143,"coord":{"lon":37.728889,"lat":55.800835}},
{"country":"UA","name":"Alupka","_id":713514,"coord":{"lon":34.049999,"lat":44.416668}},
{"country":"DE","name":"Lichtenrade","_id":2878044,"coord":{"lon":13.40637,"lat":52.398441}},
{"country":"RU","name":"Zavety Il’icha","_id":464176,"coord":{"lon":37.849998,"lat":56.049999}},
{"country":"IL","name":"‘Azriqam","_id":295582,"coord":{"lon":34.700001,"lat":31.75}},
{"country":"IN","name":"Ghūra","_id":1271231,"coord":{"lon":79.883331,"lat":24.766666}}
]
These are only few entries, I have almost 1000 entries into file.
I need to apply pagination to load 100 entries.
I created codable as follows
struct Cities: Codable {
let city: [City]?
let pagination: Pagination?
}
struct City: Codable{
let country : String
let name : String
let _id : Int
let coord: [Coordinates]?
}
struct Coordinates: Codable {
let lat : Double?
let lon : Double?
}
struct Pagination: Codable {
let limit, offset: Int?
}
and my parsing method is like this,
func getDataFrom(completion: #escaping (Cities?, Error?) -> Void) {
let url = Bundle.main.url(forResource: "cities", withExtension: "json")!
let data = try! Data(contentsOf: url)
do {
let jsonDescription = try JSONDecoder().decode(Cities.self, from: data)
print(jsonDescription)
completion(jsonDescription,nil)
}
catch let jsonError {
print("Json Error:", jsonError)
}
}
When I parse the data it goes into catch block and gives this error
Json 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))
Can anyone tell me what I am doing wrong.
I need to parse this data and show on tableview with pagination.
Thanks in advance.
You are trying to decode a type of Cities.self, but your JSON is an array - it starts with "[" and ends with "]". You might want to try declaring the type [Cities].self in your decoder call.
let jsonDescription = try JSONDecoder().decode([City].self, from: data)
Edit:
#Joakim_Danielson caught an issue, you need to decode the array [City] and not [Cities], because that's what your JSON is (I edited the above line of code accordingly).
But there is another issue: the property coord in your struct City is declared as an array [Coordinates]?, but your JSON does not have an array in the "coord" key - just a single instance.
Try changing your coord property, make it a type of Coordinate?.
Related
Fetching city name from json with swift
I need help with proper parsing of OpenWeatherAPI. I need to fetch city name. I always got this line of code: 021-09-23 08:16:13.526604+0200 Clima[1943:50811] Writing analzed variants. Optional("") This is my JSON: JSON example This is my struct for api: struct WeatherData: Decodable{ let message: String? let list: [List]? } struct List: Decodable{ let name: String? } And my function for fetching json: func parseJSON(weatherData: Data) { let decoder = JSONDecoder() do{ let decodedData = try decoder.decode(WeatherData.self, from: weatherData) print(decodedData.list![0].name!) }catch{ print(error) } } I need to fetch a name. I understand that I need to make in my main struct WeatherData let for another struct and then use it. E.g. decodedData.list.name to fetch some data. One more question: should I always use ? (optional) when fetching data from JSON ?
Converting API JSON data to a Swift struct
I am using Swift for the first time and I'd like to be able to process some info from an API response into a usable Swift object. I have (for example) the following data coming back from my API: { data: [{ id: 1, name: "Fred", info: { faveColor: "red", faveShow: "Game of Thrones", faveIceCream: "Chocolate", faveSport: "Hockey", }, age: "28", location: "The Moon", },{ ... }] } In swift I have the data coming back from the API. I get the first object and I'm converting it and accessing it like so: let json = try! JSONSerialization.jsonObject(with: data, options: []) as! [String: Any] let dataParentNode = json["data"] as! [[String:Any]] let firstObject = dataParentNode[0] let _id = firstObject["id"] as? String ?? "0" let _name = firstObject["name"] as? String ?? "Unknown" This is fine until I want to start processing the sub-objects belonging to the first object so I came up with the following structs to try and make this cleaner. Please note - I don't need to process all of the JSON data coming back so I want to convert it to what I need in the structs struct PersonInfo : Codable { let faveColor: String? let faveShow: String? } struct Person : Codable { let id: String? let name: String? let info: PersonInfo? } When I take this: let json = try! JSONSerialization.jsonObject(with: data, options: []) as! [String: Any] let dataParentNode = json["data"] as! [[String:Any]] let firstObject = dataParentNode[0] and then try to convert firstObject to Person or firstObject["info"] to PersonInfo I can't seem to get it to work (I get nil). let personInfo = firstObject["info"] as? PersonInfo Can anyone advise please? I just need to get my head around taking API response data and mapping it to a given struct (with sub-objects) ignoring the keys I don't need.
You can simply use decode(_:from:) function of JSONDecoder for this: let decoder = JSONDecoder() do { let decoded = try decoder.decode([String: [Person]].self, from: data) let firstObject = decoded["data"]?.first } catch { print(error) } Even better you can add another struct to you model like this: struct PersonsData: Codable { let data: [Person] } And map your JSON using that type: let decoder = JSONDecoder() do { let decoded = try decoder.decode(PersonsData.self, from: data) let firstObject = decoded.data.first } catch { print(error) } Update: Your Person struct might need a little change, because the id property is integer in your JSON. So, it will end up like this: struct Person : Codable { let id: Int? let name: String? let info: PersonInfo? }
parsing json with dynamic keys
I have been working on this for a few hours and have not been able to find an answer. I have a JSON with dynamic keys that I am trying to parse into a struct. I thought I could keep it simple but I'm getting serialization errors. Please help - thanks {"rates":{ "btc":{"name":"Bitcoin","unit":"BTC","value":1.0,"type":"crypto"}, "eth":{"name":"Ether","unit":"ETH","value":35.69,"type":"crypto"}, }} my stuct struct CryptoCoins: Decodable { let rates: [String: [Coin]] } struct Coin: Decodable { let name: String let unit: String let value: Double let type: String } my decoder: guard let container = try? JSONDecoder().decode(CryptoCoins.self, from: json) else { completion(.failure(.serializationError)) // <- failing here return }
You're decoding the property rates into the wrong type - it's not a dictionary of String keys and an array of Coin values - it's just a single Coin value. struct CryptoCoins: Decodable { let rates: [String: Coin] // <- here } On a related note, don't hide the error with try?. Capture it and log it, if necessary: do { let cryptoCoins = try JSONDecoder().decode(CryptoCoins.self, from: json) // .. } catch { print(error) } Then you would have gotten a typeMismatch error for btc key: "Expected to decode Array<Any> but found a dictionary instead.", which would have at least given you a hint of where to look.
Decoding Error - keyNotFound when parsing JSON data
I am using NASA API in my iOS application for getting some images. My response from the server looks like: { "date": "2014-02-04T03:30:01", "id": "LC8_L1T_TOA/LC81270592014035LGN00", "resource": { "dataset": "LC8_L1T_TOA", "planet": "earth" }, "service_version": "v1", "url": "https://earthengine.googleapis.com/api/thumb?thumbid=bc77b079c8ecd07cd668c576c22b83a4&token=a16639b0d38dd68c586c24a6ee5299d9" } My request url is: https://api.nasa.gov/planetary/earth/imagery/?lon=100.75&lat=1.5&date=2014-02-01&api_key=DEMO_KEY My struct for decoding this response is: import Foundation // MARK: - EarthImages struct EarthImages: Codable { let date: String let id: String let resource: Resource let serviceVersion: String let url: String private enum CodingKeys: String, CodingKey { case date = "date" case id = "id" case resource = "resource" case serviceVersion = "service_version" case url = "url" } } The problem is - when I am trying to decode my response using the following code URLSession.shared.dataTask(with: url) { data, response, error in if let data = data { do { let earthImages = try JSONDecoder().decode(EarthImages.self, from: data) print(earthImages.url) } catch let error{ print(error) }} }.resume() I get in console. keyNotFound(CodingKeys(stringValue: "date", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"date\", intValue: nil) (\"date\").", underlyingError: nil)) I used PAW to check if I get correct response, and it works, so the problem more likely is in my code. How can I resolve this issue?
It... looks fine? (Assuming Resource is also Decodable, but that would be a separate issue). Perhaps the error is actually telling you the truth, you may be attempting to decode a JSON blob that does not have a date value. You can explicitly see what we're attempting to decode with an added print just before we attempt to decode: if let data = data { print(String(data: data, encoding: .utf8)!) do { ... Then separately, if the date field is not guaranteed to exist in every response, you should make it optional: struct EarthImages: Codable { let date: String? let id: String let resource: Resource let serviceVersion: String let url: String } Small note, string enums dont need to be redeclared if its exactly the same as the enum case: enum CodingKeys: String, CodingKey { case date // "date" is implied case id case resource case serviceVersion = "service_version" case url } Another fun fact: JSONDecoder can also convert from snake case automatically without having to define CodingKeys if every key is consistent. So you can also do: struct EarthImages: Codable { let date: String let id: String let resource: Resource let serviceVersion: String let url: String } ... do { let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase let earthImages = try decoder.decode(EarthImages.self, from: data) }
Error parsing JSON Dictionary with Swift decodable
I receive the following error: Error serialising json typeMismatch(Swift.Dictionary, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary but found an array instead.", underlyingError: nil)) Code: //--------------- struct Currency: Decodable { let symbol: String let price: String } var myDict: [Currency] = [] //--------------- func testParse(){ let jsonUrlString = "https://api.binance.com/api/v3/ticker/price" guard let url = URL(string: jsonUrlString) else { return } URLSession.shared.dataTask(with: url) { (data,response,err) in guard let data = data else { print("Error: No data to decode") return } do { let exchanges = try JSONDecoder().decode(Currency.self, from: data) let X: [Currency] = [exchanges] self.myDict = X self.testFunc() print("binance: "+self.myDict[0].symbol + ": "+self.myDict[0].price) } catch let jsonErr { print("Error serialising json",jsonErr) } } .resume() } Is the issue with my struct layout? Or would it be how I'm parsing? I'd like to get a better understanding here for future reference. Therefore, if anyone could link a good Swift 4 guide it would be greatly appreciated. Alternatively, if you could give me a detailed answer that would be great (rather than spoon feeding the answer where I don't learn).
Please read the error message carefully and learn to understand it. It's very clear. Expected to decode Dictionary but found an array instead In other words: You want to decode a dictionary (Currency) but in truth it's an array ([Currency]). In terms of Decodable a dictionary is the target struct or class. And please don't name an object as ...dict which is actually an array. var myArray = [Currency]() ... let exchanges = try JSONDecoder().decode([Currency].self, from: data) self.myArray = exchanges