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