I am very beginner in swift and I am in the process of creating a currency converter app having a double column pickerView. left of the pickerView is the base Currency and right is the target currency . I am progressing bit by bit to make sure everything is working. I could successfully send the currency rate of GBP the UILabel but when I change it to the other currencies the console gives and error. I have a CurrencyModel
import Foundation
struct CurrencyModel {
let rate: String }
and CurrencyData
import Foundation
struct Rates: Codable {
let GBP: GBP
}
struct CurrencyData: Codable {
let status: String
let base_currency_code: String
let rates: Rates
}
struct GBP: Codable {
let rate: String
}
and currencyManager below and in CurrencyManager I have a function named parseJSON. I think I somehow need to edit the parseJSON function and CurrencyData file somehow to make it work for all currencies.
func parseJSON(currencyData: Data) -> CurrencyModel? {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(CurrencyData.self, from: currencyData)
let rate = decodedData.rates.GBP.rate
let currency = CurrencyModel(rate: rate)
return currency
} catch {
delegate?.didFailWithError(error: error)
return nil
}
lastly let me send you the structure of api show in the browser.
{
"base_currency_code": "EUR",
"base_currency_name": "Euro",
"amount": "1.0000",
"updated_date": "2022-10-30",
"rates": {
"CNY": {
"currency_name": "Renminbi",
"rate": "7.2168",
"rate_for_amount": "7.2168"
},
"EUR": {
"currency_name": "Euro",
"rate": "1.0000",
"rate_for_amount": "1.0000"
},
"GBP": {
"currency_name": "Pound sterling",
"rate": "0.8612",
"rate_for_amount": "0.8612"
},
"USD": {
"currency_name": "United States dollar",
"rate": "0.9951",
"rate_for_amount": "0.9951"
so how can I edit these not to get rate only for GBP but all others too when picked by pickerView
Related
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
}
I am trying to write a Swift Codable model for the below JSON.
{
"batchcomplete": "",
"query": {
"pages": {
"26667" (The problem is here): {
"pageid": 26667,
"ns": 0,
"title": "Spain",
"contentmodel": "wikitext",
"pagelanguage": "en",
"pagelanguagehtmlcode": "en",
"pagelanguagedir": "ltr",
"touched": "2020-03-14T18:03:48Z",
"lastrevid": 945549863,
"length": 254911,
"fullurl": "https://en.wikipedia.org/wiki/Spain",
"editurl": "https://en.wikipedia.org/w/index.php?title=Spain&action=edit",
"canonicalurl": "https://en.wikipedia.org/wiki/Spain"
}
}
}
}
The problem is that one of the key changes every time I query.
Marked the in the above JSON as (The problem is here)
How to parse the above JSON file with JSONDecoder?
This can be easily parsed with libraries like SwiftyJSON.
The point is in making let pages: [String:Item] Use
// MARK: - Root
struct Root: Codable {
let batchcomplete: String
let query: Query
}
// MARK: - Query
struct Query: Codable {
let pages: [String:Item]
}
// MARK: - Item
struct Item: Codable {
let pageid, ns: Int
let title, contentmodel, pagelanguage, pagelanguagehtmlcode: String
let pagelanguagedir: String
let touched: Date
let lastrevid, length: Int
let fullurl: String
let editurl: String
let canonicalurl: String
}
let res = try JSONDecoder().decode(Root.self, from: data)
I have a very long Json array that is full of items that look like this:
[
{
"id": "sm10-1",
"name": "Pheromosa & Buzzwole-GX",
"imageUrl": "https://images.pokemontcg.io/sm10/1.png",
"subtype": "TAG TEAM",
"supertype": "Pokémon",
"hp": "260",
"retreatCost": [
"Colorless",
"Colorless"
],
"convertedRetreatCost": 2,
"number": "1",
"artist": "Mitsuhiro Arita",
"rarity": "Rare Holo GX",
"series": "Sun & Moon",
"set": "Unbroken Bonds",
"setCode": "sm10",
"text": [
"When your TAG TEAM is knocked out, your opponent takes 3 Prize Cards."
],
"types": [
"Grass"
],
"attacks": [
{
"name": "Jet Punch",
"cost": [
"Grass"
],
"convertedEnergyCost": 1,
"damage": "30",
"text": "This attack does 30 damage to 1 of your opponent's Benched Pokémon. (Don't apply Weakness and Resistance for Benched Pokémon.)"
},
{
"name": "Elegant Sole",
"cost": [
"Grass",
"Grass",
"Colorless"
],
"convertedEnergyCost": 3,
"damage": "190",
"text": "During your next turn, this Pokémon's Elegant Sole attack's base damage is 60."
},
{
"name": "Beast Game-GX",
"cost": [
"Grass"
],
"convertedEnergyCost": 1,
"damage": "50",
"text": "If your opponent's Pokémon is Knocked Out by damage from this attack, take 1 more Prize card. If this Pokémon has at least 7 extra Energy attached to it (in addition to this attack's cost), take 3 more Prize cards instead. (You can't use more than 1 GX attack in a game.)"
}
],
"weaknesses": [
{
"type": "Fire",
"value": "×2"
}
],
"imageUrlHiRes": "https://images.pokemontcg.io/sm10/1_hires.png",
"nationalPokedexNumber": 794
}
]
That is just one item of hundreds in the array. What I want to do is grab specific values from each item (i.e. name, imageUrl, supertype, hp, rarity, set) and send them to a struct which will then be added to an array of such structs.
What I currently have prints just prints out all of the json data and I can not figure out how to get individual data and create an array of structs for each individual card.
Here is the code I have currently:
//[TEST] READING JSON FILE LOCALLY
struct card: Decodable {
let name: String
let imageUrl: String
let supertype: String
let artist: String
let rarity: String
let set: String
let types: Array<String>
}
func loadJsonInfo() {
do{
let data = try Data.init(contentsOf: URL.init(fileURLWithPath: Bundle.main.path(forResource: "Unbroken Bonds", ofType: "json")!))
let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
print(json)
} catch {
print(error)
}
}
Also, the json file is locally stored in my appData. Thanks in advance for your help!
Give a try to https://quicktype.io/
You put json there. And get all necessary data structures to decode json
To decode a JSON with Decodable type, you need to use JSONDecoder's decode(_:from:) method.
Update your loadJsonInfo() method to,
func loadJsonInfo() {
if let file = Bundle.main.url(forResource: "Unbroken Bonds", withExtension: "json") {
do {
let data = try Data(contentsOf: file)
let arr = try JSONDecoder().decode([Card].self, from: data)
print(arr)
} catch {
print(error)
}
}
}
Note: Use first letter capital while creating a type, i.e. use Card instead of card.
Parsing Code when you have DATA from server. I also removed force unwrapped ! so it won't crash in absence of file
func loadJsonInfo() {
if let path = Bundle.main.path(forResource: "Unbroken Bonds", ofType: "json") {
do {
let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .alwaysMapped)
let result = try JSONDecoder().decode(ResultElement.self, from: data)
} catch let error {
print(error)
}
}
}
Your Model
import Foundation
// MARK: - ResultElement
struct ResultElement: Codable {
let id, name: String?
let imageURL: String?
let subtype, supertype, hp: String?
let retreatCost: [String]?
let convertedRetreatCost: Int?
let number, artist, rarity, series: String?
let resultSet, setCode: String?
let text, types: [String]?
let attacks: [Attack]?
let weaknesses: [Weakness]?
let imageURLHiRes: String?
let nationalPokedexNumber: Int?
enum CodingKeys: String, CodingKey {
case id, name
case imageURL = "imageUrl"
case subtype, supertype, hp, retreatCost, convertedRetreatCost, number, artist, rarity, series
case resultSet = "set"
case setCode, text, types, attacks, weaknesses
case imageURLHiRes = "imageUrlHiRes"
case nationalPokedexNumber
}
}
// MARK: - Attack
struct Attack: Codable {
let name: String?
let cost: [String]?
let convertedEnergyCost: Int?
let damage, text: String?
}
// MARK: - Weakness
struct Weakness: Codable {
let type, value: String?
}
typealias Result = [ResultElement]
I have defined the data model for packages data, but need to define UITableview sections on the basis of
subscriptiontype = 'Yearly', 'Monthly', 'Weekly'
Getting an error of - Cannot assign value of type '[Package]' to type '[[String : String]]?'. How can I assign it to tableview sections.
Code:
var packag = [Package]()
enum TableSection: Int {
case subscriptionType = 0, yearly, monthly, weekly, total
}
var data = [TableSection: [[String: String]]]()
func sortData() {
data[.yearly] = packag.filter({ $0.subscriptionType == "yearly" })
data[.monthly] = packag.filter({ $0.subscriptionType == "monthly" })
data[.weekly] = packag.filter({ $0.subscriptionType == "weekly" })
}
Updated Code - viewdidload():
override func viewDidLoad() {
super.viewDidLoad()
tableview.dataSource = self
tableview.delegate = self
//fetchData()
if let path = Bundle.main.path(forResource: "packageList", ofType: "json") {
do {
let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom{ decoder -> Date in
let container = try decoder.singleValueContainer()
let dateStr = try container.decode(String.self)
return Date(timeIntervalSince1970: TimeInterval(dateStr)!)
}
let jSON = try? decoder.decode(Root.self, from: data)
if let packages = jSON?.packages {
self.sortData(packages: packages)
print(packages)
}
} catch {
// handle error
print(Error.self)
}
}
}
Root Model:
struct Root : Codable {
let packages : [Package]
}
Packages Model:
struct Package : Codable {
let availableUntil : Date
let benefits : [String]?
let desc : String
let didUseBefore : Bool
let name : String
let price : Double
let subscriptionType : String
let tariff : Tariff
}
Traiff Model:
struct Tariff : Codable {
let data : String
let sms : String
let talk : String
}
Updated PackageJson Data:
{ "packages": [
{
"name": "Platinum Maksi 6 GB",
"desc": "Zengin içerikli Platinum Maksi Paketi ile Turkcell Uygulamalarının keyfini sürün!",
"subscriptionType": "monthly",
"didUseBefore": true,
"benefits": [
"TV+",
"Fizy",
"BiP",
"lifebox",
"Platinum",
"Dergilik"
],
"price": 109.90,
"tariff": {
"data": "6144",
"talk": "2000",
"sms": "100"
},
"availableUntil": "1558131150"
},
{
"name": "Platinum Maksi 8 GB",
"desc": "Zengin içerikli Platinum Maksi Paketi ile Turkcell Uygulamalarının keyfini sürün!",
"subscriptionType": "monthly",
"didUseBefore": false,
"benefits": [
"TV+",
"Fizy",
"BiP",
"lifebox",
"Platinum",
"Dergilik"
],
"price": 129.90,
"tariff": {
"data": "8192",
"talk": "2000",
"sms": "100"
},
"availableUntil": "1555060350"
},
{
"name": "Platinum Maksi 12 GB",
"desc": "Zengin içerikli Platinum Maksi Paketi ile Turkcell Uygulamalarının keyfini sürün!",
"subscriptionType": "yearly",
"didUseBefore": false,
"benefits": [
"TV+",
"Fizy",
"BiP",
"lifebox",
"Platinum",
"Dergilik"
],
"price": 109.90,
"tariff": {
"data": "12288",
"talk": "2000",
"sms": "100"
},
"availableUntil": "1555060350"
},
The problem is that you are trying to assign a Package object to a value that is expecting an array of dictionary.
Your data variable is a dictionary that has a TableSection as a key, and an array of dictionaries as the value which you have defined by writing [[String: String]]. Then in your sortData function you're trying to assign a value to various data keys, but you're assigning a Package item to it when it's expecting an array of dictionaries.
What will work is if you change your data definition to
var data = [TableSection: Package]()
Model
import Foundation
struct JSON: Codable {
let packages: [Package]
}
struct Package: Codable {
let name, desc, subscriptionType: String
let didUseBefore: Bool
let benefits: [String]
let price: Double
let tariff: Tariff
let availableUntil: String
}
struct Tariff: Codable {
let data, talk, sms: String
}
Enum
enum TableSection: Int {
case subscriptionType = 0, yearly, monthly, weekly, total
}
Data
var data = [TableSection: [Package]]()
Parsing
if let path = Bundle.main.path(forResource: "document", ofType: "json") {
do {
let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
let jSON = try? JSONDecoder().decode(JSON.self, from: data)
if let packages = jSON?.packages {
self.sortData(packages: packages)
}
} catch {
// handle error
}
}
Sorting
private func sortData(packages : [Package]) {
data[.yearly] = packages.filter({ $0.subscriptionType == "yearly" })
data[.monthly] = packages.filter({ $0.subscriptionType == "monthly" })
data[.weekly] = packages.filter({ $0.subscriptionType == "weekly" })
print("Data is \(data)")
}
I use LOCAL JSON, you can use server data.
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"
}
}