Struct in Swift accesing multi-nested struct - ios

I am learning Swift and would like help on the following:
I am reading in the following JSON file format:
{
"usd":{
"code":"USD",
"rate":0.087722492431105,
"inverseRate":11.399584898769
},
"eur":{
"code":"EUR",
"rate":0.074110125979945,
"inverseRate":13.493432736447
}
}
Struct Currencies: Codable {
let usd: USD
let eur: EUR
struct USD: Codable {
let name: String
let rate: Double
let inverseRate: Double
}
struct EUR: Codable {
let name: String
let rate: Double
let inverseRate: Double
}
I would like to be able to pass in a parameter variable XXXXX that would let me print out for example (Currencies.XXXXX.rate), where I could let XXXXX to be eur, usd, etc. What would be the best way to approach this dataset and/or problem of calling the right variable?
Thanks in advance

I elaborate a little for you.
So when you decode to your first struct, you are decoding to type USD for the string usd in your Json. USD also has properties they are used to decode the dictionary for use in JSON.
let currencies = // Your decode strategy
currencies.usd //is a struct of USD
currencies.usd.rate //calls the property rate on type USD on type Currencies
If you're asking about decode strategy, you may want to rephrase the question. For example:
let json = """
{
"usd":{
"code":"USD",
"rate":0.087722492431105,
"inverseRate":11.399584898769
},
"eur":{
"code":"EUR",
"rate":0.074110125979945,
"inverseRate":13.493432736447
}
}
""".data(using: .utf8)
let cur = try? JSONDecoder().decode(Currencies.self, from: json!)
print( cur!.usd.rate )

You can try
let res = try JSONDecoder().decode([String:Currency].self,from:data)
print(Array(res.keys))
print(res["usd"])
With
struct Currency: Codable {
let name: String
let rate,inverseRate: Double
}

Related

Decoding JSON APIs in Swift - Duplicate Struct Names

My app parses a number of APIs in Swift some of which share similar names for dictionaries and/or arrays.
I can't control the names of the incoming JSON dictionaries arrays as they come from API endpoints. Currently I have one utility class that processes all these APIs. How can I parse APIs that share common names?
Here is what I'm using for one API.
struct aVideo: Codable {
let page, totalResults, totalPages: Int
let results: [Result]
enum CodingKeys: String, CodingKey {
case page
case results
}
}
// MARK: - Result
struct Result: Codable {
let popularity: Double
let voteCount: Int
let video: Bool
}
This other API uses the same key, Result and it is throwing an error.
// MARK: - WordInfo
struct WordInfo: Codable {
let word: String
let results: [Result]
let frequency: Double
}
// MARK: - Result
struct Result: Codable {
let definition, partOfSpeech: String
let synonyms, entails, hasTypes, derivation: [String]
}
Thanks for any suggestions.
The JSON itself doesn't define the name of the structure -- only the name of the property that holds it, so you're safe to redefine the names of your structs as needed:
struct WordInfo: Codable {
let word: String
let results: [WordInfoResult]
let frequency: Double
}
// MARK: - Result
struct WordInfoResult: Codable {
let definition, partOfSpeech: String
let synonyms, entails, hasTypes, derivation: [String]
}
The decoder will know that when it gets to results on WordInfo that it should be parsed as the type [WordInfoResult] because that's how you've defined it.

How I can decode and access Double from JSON using Alamofire and Swift Decode

I try do decode and access specific exchange rate for specific currency using Alamofire and Swift decode:
this is my model:
struct Listek: Codable {
let base: String
let date: String
let rates: [String: Double]
enum CodingKeys: String, CodingKey {
case base = "base"
case date = "date"
case rates = "rates"
}
}
this is Alamofire API call + decode
let apiToContact = "https://api.exchangeratesapi.io/latest"
AF.request(apiToContact).responseJSON { (response) in
print(response)
guard let data = response.data else { return }
do {
let st = try JSONDecoder().decode(Listek.self, from: data)
print (st.rates)
print (st.base)
print (st.date)
}
catch {
print("error")
}
So far so good, but I fail in accessing the single currency and its rate value. I would like declare a variable "JPYrate" with value of JPY rate from JSON. Can you please navigate me?
You can simply get the value corresponding to key JPY from rates Dictionary like so,
let JPYrate = st.rates["JPY"]
Also, there is no need to create enum CodingKeys, if the key names are same as the property names. So, your struct Listek looks like,
struct Listek: Codable {
let base: String
let date: String
let rates: [String:Double]
}
exchangeratesapi seems to send consistent data. if so my suggestion is to decode the rates into a dedicated struct
struct Listek: Decodable {
let base: String
let date: String
let rates: Rates
}
struct Rates: Decodable {
let CAD, HKD, ISK, PHP, DKK, HUF, CZK, AUD, RON, SEK, IDR, INR, BRL, RUB, HRK, JPY, THB, CHF, SGD, PLN, BGN, TRY, CNY, NOK, NZD, ZAR, USD, MXN, ILS, GBP, KRW, MYR : Double
}
Then you can get the rate directly
print(st.rates.JPY)
First, you can make a computed property to access the JPY rate:
var jpyRate: Double? { rates["JPY"] }
Or parse the rates into a specific type as recommended by #vadian.
Second, you can use Alamofire's responseDecodable method to do the decoding automatically.
AF.request(apiToContact).responseDecodable(of: Listek.self) { response in
debugPrint(response)
}

How to parse through JSON with [] brackets?

I'm parsing through JSON on a website like so (under a request.httpMethod = "GET" in Swift
):
let example = json.data.first?.TShirtPrice
The JSON I'm getting is structured like so
{"returned":1,"data":[{"TShirtPrice":"5"}]}
But I have a new JSON set that is structured without [] brackets like so:
{"returned":1,"base":"USD","data":{"TShirtPrice":"3.448500"}}
The same exact code doesn't let me get the price of the shirt anymore -- what is the fix? Thank you!
This is my code
if let data = data {
do {
let json = try JSONDecoder().decode(Root.self,from: data)
let price = json.data.first?.TShirtPrice
struct Root: Codable {
let data: [Datum]
}
struct Datum: Codable {
let TShirtPrice: String
}
Assuming your data model is something as follows You can be using Struct or Class it's not an issue.
struct Root: Decodable {
let returned: Int?
let base: String?
let data: Price?
}
struct Price: Codable {
let TShirtPrice: String?
}
Sample JSON Sting is as follows
let jsonString = """
{
"returned": 1,
"base": "USD",
"data": {
"TShirtPrice": "3.448500"
}
}
"""
You just have to change the way data is parsed by making change in data model as given above and way to access the data as given below
if let data = jsonString.data(using: .utf8) {
let myObject = try JSONDecoder().decode(Root.self, from: data)
print(myObject.data?.TShirtPrice)
}
In your case it will look like this
if let data = data {
do {
let json = try JSONDecoder().decode(Root.self,from: data)
let Price = json.data?.TShirtPrice
}
}
What is changed here?
As your price data was in format of Array the code was written accordingly and as per new data it's not an Array anymore so you have to adapt those changes app side also.

Casting to object with Codable

All my JSON responses follow the same structure:
"success": <http code>,
"data": [
]
Where the data sent back can vary. Sometimes it can contain Users, sometimes Comments, etc. So I want to create a Codable struct that is flexible to handle the various types of objects being sent back in the data array.
Here is my current struct:
struct BasicResponse: Codable {
let success: Int
let data: [User]
}
As you can see, it currently only handles User data being sent back.
Then, I read the JSON data like this (through Alamofire/Moya):
var users = [User]()
let results = try JSONDecoder().decode(BasicResponse.self, from: response.data)
self.users.append(contentsOf: results.data)
How can I change my struct file to be more flexible, and how would I then cast the JSON response to the desired object?
So, without going through a lot of design cycles and straight off the top my head, I'd consider trying Swift's generic support, for example...
struct BasicResponse<DataType>: Codable where DataType: Codable {
let success: Int
let data: [DataType]
}
Then you just need to define the implementation of DataTypes you want to use
struct User: Codable {
var name: String
}
And decode it...
let decoder = JSONDecoder()
let response = try decoder.decode(BasicResponse<User>.self, from: data)
print(response.data[0].name)
Now, I just threw this into a Playground and tested it with some basic data...
struct User: Codable {
var name: String
}
struct BasicResponse<T>: Codable where T: Codable {
let success: Int
let data: [T]
}
let data = "{\"success\": 200, \"data\": [ { \"name\":\"hello\" }]}".data(using: .utf8)!
let decoder = JSONDecoder()
do {
let response = try decoder.decode(BasicResponse<User>.self, from: data)
response.data[0].name
} catch let error {
print(error)
}
You might need to "massage" the design to better meet your needs, but it might give you a place to start

JSON Parsing Help in Swift 4 - Data Structure Issue?

New on here and to Swift so please go easy on me..
Am a bit stuck when trying to parse JSON which contains nested dictionaries. I imagine its something wrong with the data strutures I have created and I have tryed everthing to rectify but still getting the same issue.
This is the JSON api I am trying to work with:
https://api.coindesk.com/v1/bpi/currentprice.json
These are the data structures I have created to model this:
struct base: Decodable {
let disclaimer: String
let bpi: [Bpi]
}
struct Bpi: Decodable {
let USD: [USD]
}
struct USD: Decodable {
let rate_float: Float
}
And here is my code in the VC :
override func viewDidLoad() {
super.viewDidLoad()
let jsonURLString = "https://api.coindesk.com/v1/bpi/currentprice.json"
guard let url = URL(string: jsonURLString) else {return}
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else {return}
do {
let bitcoinData = try JSONDecoder().decode(base.self, from: data)
print(bitcoinData.bpi)
} catch {
print("error")
}
} .resume() // Fires off the session
}
I can grab the data from the disclaimer string or the other strings in the root dictionary but that is it. I cannot parse anything further with the nested dictonaries - it just throws back the catch error.
Here is the JSON:
{
"time": {
"updated": "Nov 2, 2017 06:08:00 UTC",
"updatedISO": "2017-11-02T06:08:00+00:00",
"updateduk": "Nov 2, 2017 at 06:08 GMT"
},
"disclaimer": "This data was produced from the CoinDesk Bitcoin Price Index (USD). Non-USD currency data converted using hourly conversion rate from openexchangerates.org",
"chartName": "Bitcoin",
"bpi": {
"USD": {
"code": "USD",
"symbol": "$",
"rate": "6,889.4013",
"description": "United States Dollar",
"rate_float": 6889.4013
},
"GBP": {
"code": "GBP",
"symbol": "£",
"rate": "5,184.4053",
"description": "British Pound Sterling",
"rate_float": 5184.4053
},
"EUR": {
"code": "EUR",
"symbol": "€",
"rate": "5,910.4587",
"description": "Euro",
"rate_float": 5910.4587
}
}
}
Is there something I am clearly doing wrong here?
Thanks for the help in advance and sorry if my formatting sucks!
Try following model, with this it works - both bpi and USD are not arrays, just single values:
struct base: Decodable {
let disclaimer: String
let bpi: Bpi
}
struct Bpi: Decodable {
let USD: USD
}
struct USD: Decodable {
let rate_float: Float
}
Dictionaries (Dictionary<K,V>) are implicitly Decodable compliant if both generic types K and V are decodable.
Assuming you create a struct Coin for the currencies
struct Coin: Decodable {
private enum CodingKeys : String, CodingKey {
case code, symbol, rate, description, rateFloat = "rate_float"
}
let code : String
let symbol : String
let rate : String
let description : String
let rateFloat : Float
}
you can easily decode the currency dictionaries as [String:Coin] without any additional code
struct Base: Decodable {
private enum CodingKeys : String, CodingKey {
case disclaimer, coins = "bpi"
}
let disclaimer: String
let coins: [String:Coin]
}
And use it
let bitcoinData = try JSONDecoder().decode(Base.self, from: data)
print(bitcoinData.coins)
Alternatively if you want the currencies as an array of Coin you can write a custom initializer and map the dictionary values to an array.
This example decodes also the updatedISO value in the time dictionary
struct Base: Decodable {
struct Time : Decodable {
private enum CodingKeys : String, CodingKey {
case updated = "updatedISO"
}
let updated : Date
}
private enum CodingKeys : String, CodingKey {
case disclaimer, bpi, time
}
let disclaimer: String
let coins: [Coin]
let updated : Date
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
disclaimer = try container.decode(String.self, forKey: .disclaimer)
let bpi = try container.decode([String:Coin].self, forKey: .bpi)
coins = Array(bpi.values.sorted(by: {$0.code < $1.code}))
let time = try container.decode(Time.self, forKey: .time)
updated = time.updated
}
}
And use this example
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let bitcoinData = try decoder.decode(Base.self, from: data)
print(bitcoinData.coins)
You declared your bpi & use properties as arrays but they were dictionaries (nested json objects). If you have sample JSON you can try this converter next time: https://danieltmbr.github.io/JsonCodeGenerator/
It generates the following output:
struct Root: Codable {
let time: Time
let disclaimer: String
let chartName: String
let bpi: Bpi
}
struct Time: Codable {
let updated: String
let updatedISO: String
let updateduk: String
}
struct Bpi: Codable {
let USD: USD
let GBP: USD
let EUR: USD
}
struct USD: Codable {
let code: String
let symbol: String
let rate: String
let description: String
let rateFloat: Double
private enum CodingKeys: String, CodingKey {
case code
case symbol
case rate
case description
case rateFloat = "rate_float"
}
}

Resources