How to customize raw value in an enum - json

What is my json looks like :
{
"2019-08-27 19:00:00": {
"temperature": {
"sol":292
}
}
,
"2019-08-28 19:00:00": {
"temperature": {
"sol":500
}
}
}
Here is a method to get the current next five days in the format needed :
func getFormatedDates() -> [String] {
let date = Date()
let format = DateFormatter()
format.dateFormat = "yyyy-MM-dd"
var dateComponents = DateComponents()
var dates = [String]()
for i in 0...4 {
dateComponents.setValue(i, for: .day)
guard let nextDay = Calendar.current.date(byAdding: dateComponents, to: date) else { return [""] }
let formattedDate = format.string(from: nextDay)
dates.append(formattedDate + " " + "19:00:00")
}
return dates
}
Because date key in the API constantly changes, I need dynamic keys. I would like to use this method inside an enum like in my Model :
var dates = getFormatedDates()
let firstForcast: FirstForcast
let secondForcast: SecondForcast
enum CodingKeys: String, CodingKey {
case firstForcast = dates[0]
case secondForcast = dates[1]
}
Any ideas ?

Create related types as below,
// MARK: - PostBodyValue
struct PostBodyValue: Codable {
let temperature: Temperature
}
// MARK: - Temperature
struct Temperature: Codable {
let sol: Int
}
typealias PostBody = [String: PostBodyValue]
and decode like this,
do {
let data = // Data from the API
let objects = try JSONDecoder().decode(PostBody.self, from: data)
for(key, value) in objects {
print(key)
print(value.temperature.sol)
}
} catch {
print(error)
}

Related

turning the json result into dynamic

i have a very complicated issue for a beginner. firstly I have this result from json
{
"success": true,
"timeframe": true,
"start_date": "2018-01-01",
"end_date": "2018-01-05",
"source": "TRY",
"quotes": {
"2018-01-01": {
"TRYEUR": 0.21947
},
"2018-01-02": {
"TRYEUR": 0.220076
},
"2018-01-03": {
"TRYEUR": 0.220132
},
"2018-01-04": {
"TRYEUR": 0.220902
},
"2018-01-05": {
"TRYEUR": 0.222535
}
}
}
and when I use https://app.quicktype.io to create the object for me it gives this and that is right.
import Foundation
// MARK: - APIResult
struct APIResult {
let success, timeframe: Bool
let startDate, endDate, source: String
let quotes: [String: Quote]
}
// MARK: - Quote
struct Quote {
let tryeur: Double
}
but I don't want my currencies hardcoded like this so if I choose from: USD to : EUR in my app I want to get the result under Quote as USDEUR. And I also know that if I change anything in this struct it won't work. So how will make those currency selections dynamic to make it work in different currencies. This is a currency converter app and I want to get these rates and reflect it on a chart in my app. Thank you.
Edit: I think I need to get used to using stack overflow properly. Sorry for any inconvenience . At last I could get the dates and rates written in the console. my question is now :
how can i get these results in the console passed into my charts x(dates) and y axis(rates) ?
["2022-12-22": 19.803011, "2022-12-18": 19.734066, "2022-12-23": 19.907873, "2022-12-21": 19.79505, "2022-12-24": 19.912121, "2022-12-17": 19.756527, "2022-12-16": 19.752446, "2022-12-25": 19.912121, "2022-12-19": 19.794356, "2022-12-20": 19.824031]
this is the func i get these
func updateChart () {
let date = Date()
let endDate = formatter.string(from: date)
let startDate = Calendar.current.date(byAdding: .day, value: -9, to: date)
let startDatee = formatter.string(from: startDate ?? Date())
print(endDate)
print(startDatee)
let result: () = currencyManager.fetchRatesForTimeframe(from: from, to: to, startDate: startDatee, endDate: endDate)
print(result)
}
and this is my previously created and hardcoded charts
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
lineChart.frame = CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: 240)
lineChart.center = view.center
view.addSubview(lineChart)
var entries = [ChartDataEntry]()
for x in 0..<10 {
entries.append(ChartDataEntry(x: Double(x), y: Double(x)))
}
let set = LineChartDataSet(entries: entries)
set.colors = ChartColorTemplates.material()
let data = LineChartData(dataSet: set)
lineChart.data = data
}
Decodable is pretty versatile and highly customizable.
Write a custom initializer and map the quote dictionary to an array of Quote instances which contains the date and the quote. The key TRYEUR is irrelevant and will be ignored.
let jsonString = """
{
"success": true,
"timeframe": true,
"start_date": "2018-01-01",
"end_date": "2018-01-05",
"source": "TRY",
"quotes": {
"2018-01-01": {
"TRYEUR": 0.21947
},
"2018-01-02": {
"TRYEUR": 0.220076
},
"2018-01-03": {
"TRYEUR": 0.220132
},
"2018-01-04": {
"TRYEUR": 0.220902
},
"2018-01-05": {
"TRYEUR": 0.222535
}
}
}
"""
struct APIResult: Decodable {
private enum CodingKeys: String, CodingKey {
case success, timeframe, startDate = "start_date", endDate = "end_date", source, quotes
}
let success, timeframe: Bool
let startDate, endDate, source: String
let quotes: [Quote]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
success = try container.decode(Bool.self, forKey: .success)
timeframe = try container.decode(Bool.self, forKey: .timeframe)
startDate = try container.decode(String.self, forKey: .startDate)
endDate = try container.decode(String.self, forKey: .endDate)
source = try container.decode(String.self, forKey: .source)
let quoteData = try container.decode([String: [String:Double]].self, forKey: .quotes)
quotes = quoteData.compactMap({ (key, value) in
guard let quote = value.values.first else { return nil }
return Quote(date: key, quote: quote)
}).sorted{$0.date < $1.date}
}
}
struct Quote {
let date: String
let quote: Double
}
do {
let result = try JSONDecoder().decode(APIResult.self, from: Data(jsonString.utf8))
print(result)
} catch {
print(error)
}
I changed my api provider now but this new one is not that much different than the previous one . this is the response I get inside browser
{
"success": true,
"timeseries": true,
"start_date": "2022-04-01",
"end_date": "2022-04-05",
"base": "USD",
"rates": {
"2022-04-01": {
"TRY": 14.686504
},
"2022-04-02": {
"TRY": 14.686504
},
"2022-04-03": {
"TRY": 14.686145
},
"2022-04-04": {
"TRY": 14.696501
},
"2022-04-05": {
"TRY": 14.72297
}
}
}
this is my object
struct APIResult: Codable {
let timeseries: Bool
let success: Bool
let startDate: String
let endDate: String
let base: String
var rates: [String:[String:Double]]
}
and this is my code inside VC to get the current date and 10 days before and I can see it printed in the console.
lazy var formatter: DateFormatter = {
let formatter = DateFormatter()
formatter.timeZone = .current
formatter.locale = .current
formatter.dateFormat = "yyyy-MM-dd"
return formatter
}()
func updateChart () {
let date = Date()
let endDate = formatter.string(from: date)
let startDate = Calendar.current.date(byAdding: .day, value: -10, to: date)
let startDatee = formatter.string(from: startDate ?? Date())
print(endDate)
print(startDatee)
currencyManager.fetchRatesForTimeframe(from: from, to: to, startDate: startDatee, endDate: endDate)
lastly these are the codes inside my other file called CurrencyManager
func fetchRatesForTimeframe(from: String, to: String, startDate: String, endDate:String) {
let urlString = "\(timeFrameUrl)base=\(from)&symbols=\(to)&start_date=\(startDate)&end_date=\(endDate)&apikey=\(api.convertApi)"
performRequestforTimeframe(with: urlString)
}
func performRequestforTimeframe(with urlString: String) {
if let url = URL(string: urlString) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { data, response, error in
if error != nil {
print(error!)
} else {
if let safeData = data {
if let timeFrameRates = parseJSONForTimeframe(currencyData: safeData) {
print(timeFrameRates)
self.delegate?.didGetTimeframeRates(timeFrameRates)
}
}
}
}
task.resume()
}
}
func cut(_ value: [String: [String: Double]]) -> [String: [String: Double]] {
let dic = value
.sorted(by: { $0.0 < $1.0 })[0..<10] // <-- last 10 results
.reduce(into: [String: [String: Double]]()) {
$0[$1.key] = $1.value
}
return dic
}
func parseJSONForTimeframe(currencyData: Data) -> APIResult? {
let decoder = JSONDecoder()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
decoder.dateDecodingStrategy = .formatted(dateFormatter)
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let decoder = JSONDecoder()
var jsondata = try decoder.decode(APIResult.self, from: currencyData)
jsondata.rates = cut(jsondata.rates)
return jsondata
} catch {
return nil
}
}
}
why I can't get the result for print(timeFrameRates) inside func performRequestforTimeframe ? what s missing?

please help get data from json in swift

I parsed data from a json file, but I don't know how to get these variables.
Need charcode, name and value.
I need to display them in a table using swiftui. I got a mess in the console and I don't know how to get to this data
this is struct
import Foundation
struct CurrencyModel: Codable {
let valute: [String : Valute]
enum CodingKeys: String, CodingKey {
case valute = "Valute"
}
}
struct Valute: Codable {
let charCode, name: String
let value: Double
enum CodingKeys: String, CodingKey {
case charCode = "CharCode"
case name = "Name"
case value = "Value"
}
}
and this is parser
class FetchDataVM: ObservableObject {
var valueData = [String : Any]()
init() {
fetchCurrency()
}
func fetchCurrency() {
let urlString = "https://www.cbr-xml-daily.ru/daily_json.js"
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!) {data, _, error in
DispatchQueue.main.async {
if let data = data {
do {
let decoder = JSONDecoder()
let decodedData = try decoder.decode(CurrencyModel.self, from: data)
print(decodedData)
} catch {
print("Error! Something went wrong.")
}
}
}
}.resume()
}
}
As all needed information is in the Valute struct you need only the values of the valute dictionary. Replace
var valueData = [String : Any]()
with
#Published var valueData = [Valute]()
and after the line print(decodedData) insert
self.valueData = decodedData.valute.values.sorted{$0.name < $1.name}
or
self.valueData = decodedData.valute.values.sorted{$0.charCode < $1.charCode}
In the view you can iterate the array simply with a ForEach expression

Swift Add codable to array of codable

I looking to convert a row of SQlite to JSON array.
Example:
{
"0": {
"room_id": "5034"
},
"1": {
"room_id": "5199"
},
"2": {
"room_id": "5156"
}
}
Swift4 code:
typealias Rooms = [String: Room]
struct Room: Codable {
let roomID: String
enum CodingKeys: String, CodingKey {
case roomID = "room_id"
}
}
var rooms = [Rooms]()
for room in try (db?.prepare(isco_room_time))! {
let export: Room = Room(roomID: room[room_id])
rooms.append(export)
}
My error (on line rooms.append) :
Cannot convert value of type 'ViewController.Room' to expected argument type 'ViewController.Rooms' (aka 'Dictionary')
Why do you need this?
typealias Rooms = [String: Room]
If you want an array of Codable objects you don't need the alias
Changing
var rooms = [Rooms]()
To
var rooms = [Room]()
Will work.
Since rooms is a dictionary you shouldn't use append
for room in try (db?.prepare(isco_room_time))! {
let export: Room = Room(roomID: room[room_id])
rooms[room_id] = export
}
You can try
var counter = 0
var rooms = [String:Room]()
do {
guard let dbs = db else { return }
let res = try dbs.prepare(isco_room_time)
for room in res {
let export: Room = Room(roomID: room.id)
rooms["\(counter)"] = export
counter += 1
}
}
catch {
print(error)
}

How to filter invalid data Using swift 4.1 codable ( json decode)

I got to know struct "Codable" in swift 4.0, *.
So, I tried that when decode josn.
if let jsonData = jsonString.data(using: .utf8) {
let decodingData = try? JSONDecoder().decode(SampleModel.self, from: jsonData)
}
Example sample data model below.
struct SampleModel : Codable {
var no: Int?
var category: Int?
var template_seq: Int?
}
And sample json data is .. below.
{
"data": {
"result" : 1
"total_count": 523,
"list": [
{
"no": 16398,
"category" : 23,
"template_seq" : 1
},
{
"no": -1,
"category" : 23,
"template_seq" : 1
}
]
}
}
But i want filtering wrong data.
If the value of "no" is less than or equal to 0, it is an invalid value.
Before not using codable...below.
(using Alamifre ison response )
guard let dictionaryData = responseJSON as? [String : Any] else { return nil }
guard let resultCode = dictionaryData["result"] as? Bool , resultCode == true else { return nil }
guard let theContainedData = dictionaryData["data"] as? [String:Any] else { return nil }
guard let sampleListData = theContainedData["list"] as? [[String : Any]] else { return nil }
var myListData = [MyEstimateListData]()
for theSample in sampleListData {
guard let existNo = theSample["no"] as? Int, existNo > 0 else {
continue
}
myListData.append( ... )
}
return myListData
how to filter wrong data or invalid data using swift 4.0 Codable ??
you can make codable for inital resonse
Here is your model:
import Foundation
struct Initial: Codable {
let data: DataModel?
}
struct DataModel: Codable {
let result, totalCount: Int
let list: [List]?
enum CodingKeys: String, CodingKey {
case result
case totalCount = "total_count"
case list
}
}
struct List: Codable {
let no, category, templateSeq: Int
enum CodingKeys: String, CodingKey {
case no, category
case templateSeq = "template_seq"
}
}
extension Initial {
init(data: Data) throws {
self = try JSONDecoder().decode(Initial.self, from: data)
}
}
And use it like that :
if let initail = try? Initial.init(data: data) , let list = initail.data?.list {
var myListData = list.filter { $0.no > 0 }
}
Yes, you have to use filters for that with codable:
1: Your struct should be according to your response like that:
struct SampleModel : Codable {
var result: Int?
var total_count: Int?
var list: [List]?
}
struct List : Codable {
var no: Int?
var category: Int?
var template_seq: Int?
}
2: Parse your response using a codable struct like that:
do {
let jsonData = try JSONSerialization.data(withJSONObject: dictionaryData["data"] as Any, options: JSONSerialization.WritingOptions.prettyPrinted)
let resultData = try JSONDecoder().decode(SampleModel.self, from: jsonData)
success(result as AnyObject)
} catch let message {
print("JSON serialization error:" + "\(message)")
}
3: now you can filter invalid data simply:
let filterListData = resultData.list?.filter({$0.no > 0})
let invalidData = resultData.list?.filter({$0.no <= 0})

Convert string to Date/Int/Double using codable

I am getting a response from an API but the problem is that the API is sending values back as a string of dates and doubles. I am therefore getting the error "Expected to decode Double but found a string/data instead." I have structured my struct like this to solve the problem but this seems like a patch. Is there any better way to fix this issue? I feel like apple has thought of this and included something natively to address this.
struct SimpleOrder:Codable{
var createdDate:Date! {
return createdDateString.dateFromISO8601
}
var createdDateString:String
var orderId:String!
var priceVal:Double!
var priceString:String{
didSet {
priceVal = Double(self.priceString)!
}
}
private enum CodingKeys: String, CodingKey {
//case createdDate
case createdDateString = "time"
case orderId = "id"
case priceVal
case priceString = "price"
}
}
I don't know if this is relevant but this is how it is being used. I am getting the data as a string and converting it to data which is stored in the dataFromString variable
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601 //This is irrelevant though because the date is returned as a string.
do{
let beer = try decoder.decode(SimpleOrder.self, from: dataFromString)
print ("beer is \(beer)")
}catch let error{
print ("error is \(error)")
}
As a result of using codable, I am getting an error when trying to get an empty instance of SimpleOrder. Before I was using codable, I had no issues using SimpleOrder() without any arguments.
Error: Cannot invoke initializer for type 'SimpleOrder' with no arguments
var singlePoint = SimpleOrder()
struct SimpleOrder: Codable {
var created: Date?
var orderId: String?
var price: String?
private enum CodingKeys: String, CodingKey {
case created = "time", orderId = "id", price
}
init(created: Date? = nil, orderId: String? = nil, price: String? = nil) {
self.created = created
self.orderId = orderId
self.price = price
}
}
extension SimpleOrder {
var priceValue: Double? {
guard let price = price else { return nil }
return Double(price)
}
}
extension Formatter {
static let iso8601: DateFormatter = {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
return formatter
}()
}
Decoding the json data returned by the API:
let jsonData = Data("""
{
"time": "2017-12-01T20:41:48.700Z",
"id": "0001",
"price": "9.99"
}
""".utf8)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(Formatter.iso8601)
do {
let simpleOrder = try decoder.decode(SimpleOrder.self, from: jsonData)
print(simpleOrder)
} catch {
print(error)
}
Or initialising a new object with no values:
var order = SimpleOrder()