Fetching city name from json with swift - json

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 ?

Related

Parse JSON from file using Codable swift

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

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

Trying to parse json, can't seem to parse arrays inside of an array

I've been trying to parse a part of this JSON file: https://opendata.brussels.be/api/records/1.0/search/?dataset=traffic-volume&rows=3&facet=level_of_service
I wanna get records->fields->geo_shape->coordinates but I can't seem to print these arrays inside of the "coordinates" array.. I thought it might be because the arrays inside of the coordinates do not have names, so I don't know how to make a variable for them. Got this code currently:
import UIKit
import Foundation
struct Geoshape : Codable {
let coordinates: Array<...>
}
struct Field : Codable {
let geo_shape: Geoshape
let level_of_service: String
}
struct Record: Codable {
let fields: Field
}
struct Traffic: Codable{
let records: Array<Record>
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func clickRefresh(_ sender: Any) {
guard let url = URL(string: "https://opendata.brussels.be/api/records/1.0/search/?dataset=traffic-volume&rows=3&facet=level_of_service") else { return }
let session = URLSession.shared
let task = session.dataTask(with: url){ (data, response, error) in
if let response = response {
print(response)
}
if let data = data {
let traffic = try? JSONDecoder().decode(Traffic.self, from: data)
print(traffic)
}
}
task.resume()
}
}
Clearly the Array<...> needs to be changed but I don't know to what. I've tried making an extra struct with just 1 variable (which is another array of the type Double: Double) but that does not seem to work. I was able to print everything just fine up to the point I tried to go into the coordinates array.
Anyone can help me?
Replace
let coordinates: Array<...>
with
let coordinates:[[Double]]
First of all your file in Resource contains a JSON which contains an array or Collection (typically in Swift).
One important thing: If you fail to decode an object in json you get null from all stored properties.
fail occurs when your Coding Keys does not match keys in json or type you are casting is diffrent.
In your code you fail to cast coordinates to its type. coordinates is a collection of collections of Double.
var coordinates: [[Double]]
If you want to fetch data into your models, you should conforms them to Decodable protocol which means that,JSON attributes can decode itself.
Based on Apple developer documentation:
Decodable is a type that can decode itself from an external representation.
also Codable protocol refers to Decodable and Encodable protocols. but current purpose is Decoding data.
typealias Codable = Decodable & Encodable
Your code should look like:
Swift 5
Prepared for Playground, paste this into your playground
import Foundation
struct GeoShape: Decodable {
var coordinates: [[Double]]
}
struct Field: Decodable {
var geo_shape: GeoShape
}
struct Record: Decodable {
var fields: Field
}
struct Traffic: Decodable {
var records: [Record]
}
guard let url = URL.init(string: "https://opendata.brussels.be/api/records/1.0/search/?dataset=traffic-volume&rows=3&facet=level_of_service")
else {fatalError()}
URLSession.shared.dataTask(with: url){ (data, response, error) in
if let data = data {
let traffic = try? JSONDecoder().decode(Traffic.self, from: data)
print("First coordinate is: ",traffic?.records.first?.fields.geo_shape.coordinates.first)
}
}.resume()

how to loop through structs?

I'm fetching data from coinDesk API to get bitcoin rate related to other currencies, I've created 3 structs to save this data, but it's not possible to loop through the struct to know how many items I have there...
that's my structure:
struct Response: Codable {
var bpi: currencies
}
struct currencies: Codable {
var USD: info
var GBP: info
var EUR: info
}
struct info: Codable {
var code: String
var symbol: String
var description: String
var rate_float: Float
}
To save the data from API I just use:
let jsonData = try JSONDecoder().decode(Response.self, from: data)
It saves the data with no error but, when I try to loop through this data to populate tableViewCells it doesn't work.
what I'm doing know is...
let euro = jsonData.bpi.EUR
let dollar = jsonData.bpi.USD
let gbp = jsonData.bpi.GBP
let infos = [euro,dollar,gbp]
completion(infos)
This is sending the data to my UITableView and populating, but what if I had 500 currencies? it would not be practical at all.. how could I do this in a more effective way?
Thank you in advance for the answers.
Don't put keys instead
struct Response: Codable {
let bpi: [String:Info]
}
struct Info: Codable {
let code: String
let symbol: String
let description: String
let rate_float: Float
}
Then
let jsonData = try JSONDecoder().decode(Response.self, from: data)
print(jsonData.bpi["USD"])
so for all keys
let keys = Array(jsonData.bpi.keys)
let values = Array(jsonData.bpi.values)

Swift: Check if data response is an array or a dictonary

I'm calling an API and storing the data in an array.
But if there is no data the debugger says:
Expected to decode Array but found a dictionary instead.
The fail JSON response is:
'{"status":"Failed","msg":"Sorry It\'s Not Working"}'
The success JSON response is:
'[{"id":"509","name":"TEC TEST !"#!12","sortingId":"1"},
{"id":"510","name":"TEC TEST !"#!12","sortingId":"2"},
{"id":"511","name":"TEC TEST !"#!12","sortingId":"3"},
{"id":"512","name":"TEC TEST !"#!12","sortingId":"4"},
{"id":"513","name":"TEC TEST !"#!12","sortingId":"5"},
{"id":"514","name":"TEC TEST !"#!12","sortingId":"6"},
{"id":"519","name":"TEC TEST !"#!12","sortingId":"7"}]'
So I want to switch between fetching my response as
var result:[Items]?
and
var result:Items?
if the Failed JSON gets send
I've been google´ing and searching Stackoverflow without luck
Is there an solution to say if JSON is an array or dictionary?
My Struct:
struct Items: Codable {
let id: String?
let sortingId: String?
let name: String?
let response: String?
let status: String?
let msg: String?
}
My processing of the response:
var result:[Items]?
result = try JSONDecoder().decode([Items].self, from: data!)
DispatchQueue.main.async {
for item in result! {
self.itemArray.append((name: item.name!, id: Int(item.id!)!, sortingId: Int(item.sortingId!)!))
}
}
One solution is to write a custom initializer which conditionally decodes the array or the dictionary.
Please ask the owner of the service to send more consistent JSON. It's very bad. At least the object should be always a dictionary with key status and either the array for key items or the key msg.
This code first tries to decode the array with unkeyedContainer. If it fails it decodes the dictionary.
struct Item: Decodable {
let id: String
let sortingId: String
let name: String
}
struct ItemData : Decodable {
private enum CodingKeys: String, CodingKey { case status, msg }
let status : String?
let msg: String?
var items = [Item]()
init(from decoder: Decoder) throws {
do {
var unkeyedContainer = try decoder.unkeyedContainer()
while !unkeyedContainer.isAtEnd {
items.append(try unkeyedContainer.decode(Item.self))
}
status = nil; msg = nil
} catch DecodingError.typeMismatch {
let container = try decoder.container(keyedBy: CodingKeys.self)
status = try container.decodeIfPresent(String.self, forKey: .status)
msg = try container.decodeIfPresent(String.self, forKey: .msg)
}
}
}
And call it
result = try JSONDecoder().decode(ItemData.self, from: data!)
A – probably more suitable – alternative is to catch the error in the JSONDecoder().decode line and use two simple structs
struct Item: Decodable {
let id: String
let sortingId: String
let name: String
}
struct ErrorData : Decodable {
let status : String
let msg: String
}
and call it
do {
let decoder = JSONDecoder()
do {
let result = try decoder.decode([Item].self, from: data!)
print(result)
} catch DecodingError.typeMismatch {
let result = try decoder.decode(ErrorData.self, from: data!)
print(result)
}
} catch { print(error) }
A big benefit is that all properties can be non-optional.
First of all the JSON does not contain any array. It's very very easy to read JSON. There are only 2 (two!) collection types, array [] and dictionary {}. As you can see there are no square brackets at all in the JSON string.
it will helpful