Calculating averages by date from JSON in SwiftUI - json

I have JSON data that I decoded and stored in core data that I am using to populate a SwiftUi line chart. I need to calculate the average of the data by dates. So the average of polling data by months.
JSON Data:
"president": "Joe Biden",
"subgroup": "All polls",
"modeldate": "9/9/2022",
"startdate": "1/19/2021",
"enddate": "1/21/2021",
"pollster": "Rasmussen Reports/Pulse Opinion Research",
"grade": "B",
"samplesize": 1500,
"population": "lv",
"weight": 0.33818752,
"influence": 0,
"approve": 48,
"disapprove": 45,
"adjusted_approve": 49.154266,
"adjusted_disapprove": 40.2849,
"multiversions": "",
"tracking": "T",
"url": "https://www.rasmussenreports.com/public_content/politics/biden_administration/biden_approval_index_history",
"poll_id": 74247,
"question_id": 139395,
"createddate": "January 2021",
"timestamp": "09:48:31 9 Sep 2022"
Data Model:
struct PresApprovalDTO: Hashable, Decodable {
var president: String
var subgroup: String
var startdate: String
var enddate: String
var pollster: String
var approve: Double
var disapprove: Double
var url: String
var createddate: String
var startDateConverter: Date {
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX") // set locale to reliable US_POSIX
dateFormatter.dateFormat = "MM-dd-yyyy"
let date = dateFormatter.date(from:startdate) ?? Date()
let calendar = Calendar.current
let components = calendar.dateComponents([.year, .month, .day, .hour], from: date)
let finalDate = calendar.date(from:components) ?? Date()
return(finalDate)
}
var endDateConverter: Date {
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX") // set locale to reliable US_POSIX
dateFormatter.dateFormat = "MM-dd-yyyy"
let date = dateFormatter.date(from:enddate) ?? Date()
let calendar = Calendar.current
let components = calendar.dateComponents([.year, .month, .day, .hour], from: date)
let finalDate = calendar.date(from:components) ?? Date()
return(finalDate)
}
}
Chart View Model:
import SwiftUI
import Charts
struct PresApprChart: View {
#StateObject private var pollListVM = PollsListViewModel()
var body: some View {
Chart(pollListVM.presPolls, id: \.url) { item in
LineMark(x: .value("Month", item.endDateConverter, unit: .month),
y: .value("Approval", item.approve)
)
}.frame(width: 325, height: 200)
.onAppear(perform: {
pollListVM.getAllPresPolls()
})
}
}
struct PresApprChart_Previews: PreviewProvider {
static var previews: some View {
PresApprChart()
}
}
Above is the result. There are tons of data points for each month, so I need to average them. I tried to map and divide the approved values by the date count, but it averages all the approval data points.
LineMark(x: .value("Month", item.endDateConverter, unit: .month),
y: .value("Approval", pollListVM.presPolls.map(\.approve).reduce(0.0, +) / Double(item.endDateConverter.asFormattedString().count))
)

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?

SWIFT UI request

Im trying to make a request on Swift UI in a data file, but nothing is happening on and I seem to not be getting back any data. Im not sure where the issue is, keep in mind I just started swiftUI 2days ago. Any help would be greatly appreciated.
Data Im trying to retrieve.
[
{
"id": 1119,
"make": "Audi",
"model": "100",
"generation": "100 Avant (4A,C4)",
"engine_modification": "2.8 V6 E (174 Hp) quattro Automatic",
"year": 1991,
"powertrain_architecture": "Internal Combustion engine",
"body_type": "Station wagon (estate)",
"number_of_seats": 5,
"number_of_doors": 5,
"urban_fuel_consumption": 13.5,
"extra_urban_fuel_consumption": 8,
"combined_fuel_consumption": 10,
"fuel_type": "Petrol (Gasoline)",
"acceleration": 9.5,
"top_speed": 207,
"power": 174,
"torque": 250,
"engine_location": "Front, Longitudinal",
"engine_displacement": 2771,
"number_of_cylinders": 6,
"position_of_cylinders": "V-engine",
"number_of_valves_per_cylinder": 2,
"fuel_system": "Multi-point indirect injection",
"engine_aspiration": "Naturally aspirated engine",
"kerb_weight": 1550,
"fuel_tank_capacity": 80,
"drive_wheel": "All wheel drive (4x4)",
"number_of_gears": 4,
"front_brakes": "Ventilated discs",
"rear_brakes": "Disc"
},
{
"id": 1120,
"make": "Audi",
"model": "100",
"generation": "100 Avant (4A,C4)",
"engine_modification": "2.8 V6 E (174 Hp) quattro",
"year": 1991,
"powertrain_architecture": "Internal Combustion engine",
"body_type": "Station wagon (estate)",
"number_of_seats": 5,
"number_of_doors": 5,
"urban_fuel_consumption": 13.5,
"extra_urban_fuel_consumption": 8,
"combined_fuel_consumption": 10,
"fuel_type": "Petrol (Gasoline)",
"acceleration": null,
"top_speed": null,
"power": 174,
"torque": 250,
"engine_location": "Front, Longitudinal",
"engine_displacement": 2771,
"number_of_cylinders": 6,
"position_of_cylinders": "V-engine",
"number_of_valves_per_cylinder": 2,
"fuel_system": "Multi-point indirect injection",
"engine_aspiration": "Naturally aspirated engine",
"kerb_weight": 1550,
"fuel_tank_capacity": 80,
"drive_wheel": "All wheel drive (4x4)",
"number_of_gears": 5,
"front_brakes": "Ventilated discs",
"rear_brakes": "Disc"
},
Code for request
import SwiftUI
import Foundation
struct astonMartin: Decodable, Identifiable{
let id: Int
var make: String
var model: String
var generation: String
var engine_modification: String
var year: Int
var powertrain_architecture: String
var body_type: String
var number_of_seats: Int
var number_of_doors: Int
var urban_fuel_consumption: Int
var extra_urban_fuel_consumption: Int
var combined_fuel_consumption: Int
var fuel_type: String
var acceleration: Int
var top_speed: Int
var power: Int
var torque: Int
var engine_location: String
var engine_displacement: Int
var number_of_cylinders: Int
var position_of_cylinders: String
var number_of_valves_per_cylinder: Int
var fuel_system: String
var engine_aspiration: String
var kerb_weight: Int
var fuel_tank_capacity: Int
var drive_wheel: String
var number_of_gears: Int
var front_brakes: String
var rear_brakes: String
}
class secondaryCall: ObservableObject {
#Published var cars: [astonMartin] = []
func getCars() {
guard let url = URL(string: "URL INSERTED HERE") else { fatalError("Missing URL") }
let urlRequest = URLRequest(url: url)
let dataTask = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
if let error = error {
print("Request error: ", error)
return
}
guard let response = response as? HTTPURLResponse else { return }
if response.statusCode == 200 {
guard let data = data else { return }
DispatchQueue.main.async {
do {
let decodedCars = try JSONDecoder().decode([astonMartin].self, from: data)
self.cars = decodedCars
} catch let error {
print("Error decoding: ", error)
}
}
}
}
dataTask.resume()
}
}
content view
import SwiftUI
struct ContentView: View {
#State var isLinkActive = false
#EnvironmentObject var allCars: secondaryCall
var body: some View {
NavigationView{
ZStack{
Image("car")
.resizable()
.scaledToFill()
.ignoresSafeArea(.all)
.overlay(
Rectangle()
.opacity(0.6)
.ignoresSafeArea(.all)
)
NavigationLink(destination: HomeView()) {
Text("Click to view all cars")
.foregroundColor(.white)
}
.padding()
.background(.orange)
.clipShape(Capsule()
)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(secondaryCall())
}
}
Home view:
//
// HomeView.swift
// project
//
//
import SwiftUI
struct HomeView: View {
#EnvironmentObject var allCars: secondaryCall
var body: some View {
ScrollView{
Text("All cars for Aston Martin")
.bold()
.textCase(.uppercase)
.font(.headline)
ForEach(allCars.cars){car in
HStack(alignment: .top){
Text("\(car.make)")
}
}
}
.onAppear{
allCars.getCars()
}
}
}
struct HomeView_Previews: PreviewProvider {
static var previews: some View {
HomeView()
.environmentObject(secondaryCall())
}
}
Project App
//
// projectApp.swift
// project
//
//
import SwiftUI
#main
struct projectApp: App {
var allCars = secondaryCall()
var body: some Scene {
WindowGroup {
SplashView()
.environmentObject(allCars)
}
}
}
Form your data, I see that the reason you not getting data is that because you construct some variables in struct astonMartin with wrong type compare to the JSON you given.
Given you some example
In Json: "urban_fuel_consumption": 13.5, which should be Double but in struct astonMartin you set is Int
In Json: "acceleration": null, which should be Double? because it is nil here but in struct astonMartin you set is Double
In Json: "top_speed": null, which should be Int? because it is nil here but in struct astonMartin you set is Int
And maybe more of that ...
You should recheck each type of variables in your struct
Code will change like follow
struct astonMartin: Decodable, Identifiable {
let id: Int
var make: String
var model: String
var generation: String
var engine_modification: String
var year: Int
var powertrain_architecture: String
var body_type: String
var number_of_seats: Int
var number_of_doors: Int
var urban_fuel_consumption: Double // was Int
var extra_urban_fuel_consumption: Int
var combined_fuel_consumption: Int
var fuel_type: String
var acceleration: Double? // was Int
var top_speed: Int? // was Int
var power: Int
var torque: Int
var engine_location: String
var engine_displacement: Int
var number_of_cylinders: Int
var position_of_cylinders: String
var number_of_valves_per_cylinder: Int
var fuel_system: String
var engine_aspiration: String
var kerb_weight: Int
var fuel_tank_capacity: Int
var drive_wheel: String
var number_of_gears: Int
var front_brakes: String
var rear_brakes: String
}
More over, when you parse json not right it prints error in debug mode because you currently catch error there. Make sure you notice that.

Error decoding JSON - keyNotFound(CodingKeys

When I try to decode this json:
"polls": [
{
"title": "title",
"date": "date",
"summary": "summary",
"stats": {
"total": {
"dagegen gestimmt": 139,
"nicht beteiligt": 114,
"dafür gestimmt": 454,
"enthalten": 2
},
}
}, /*<and about 76 of this>*/ ]
with this Codable:
struct poll: Codable {
var stats: stats
var title: String?
var date: String?
var summary: String?
struct stats: Codable {
var total: total
struct total: Codable {
var nays: Int
var yays: Int
var nas: Int
var abstentions: Int
private enum CodingKeys: String, CodingKey {
case yays = "dafür gestimmt"
case nays = "dagegen gestimmt"
case nas = "nicht beteiligt"
case abstentions = "enthalten"
}
}
}
}
I get the following error
keyNotFound(CodingKeys(stringValue: "dagegen gestimmt", intValue: nil)(if you need the full error text tell me)
I tried some of the answer from similar questions but nothing worked.
You apparently have occurrences of total where dagegen gestimmt is absent. So, make that an Optional, e.g. Int?:
struct Poll: Codable {
let stats: Stats
let title: String?
let date: Date?
let summary: String?
struct Stats: Codable {
let total: Total
struct Total: Codable {
let nays: Int?
let yays: Int?
let nas: Int?
let abstentions: Int?
private enum CodingKeys: String, CodingKey {
case yays = "dafür gestimmt"
case nays = "dagegen gestimmt"
case nas = "nicht beteiligt"
case abstentions = "enthalten"
}
}
}
}
I’d also suggest the following, also reflected in the above:
Start type names (e.g. your struct names) with uppercase letter;
Use let instead of var as we should always favor immutability unless you really are going to be changing these values within this struct; and
If your date is in a consistent format, I’d suggest making the date a Date type, and then you can supply the JSONDecoder a dateDecodingStrategy that matches (see sample below).
For example:
let data = """
{
"polls": [
{
"title": "New Years Poll",
"date": "2019-01-01",
"summary": "summary",
"stats": {
"total": {
"dagegen gestimmt": 139,
"nicht beteiligt": 114,
"dafür gestimmt": 454,
"enthalten": 2
}
}
},{
"title": "Caesar's Last Poll",
"date": "2019-03-15",
"summary": "summary2",
"stats": {
"total": {
"dafür gestimmt": 42
}
}
}
]
}
""".data(using: .utf8)!
struct Response: Codable {
let polls: [Poll]
}
do {
let decoderDateFormatter = DateFormatter()
decoderDateFormatter.dateFormat = "yyyy-MM-dd"
decoderDateFormatter.locale = Locale(identifier: "en_US_POSIX")
let userInterfaceDateFormatter = DateFormatter()
userInterfaceDateFormatter.dateStyle = .long
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(decoderDateFormatter)
let response = try decoder.decode(Response.self, from: data)
let polls = response.polls
for poll in polls {
print(poll.title ?? "No title")
print(" date:", poll.date.map { userInterfaceDateFormatter.string(from: $0) } ?? "No date supplied")
print(" yays:", poll.stats.total.yays ?? 0)
print(" nays:", poll.stats.total.nays ?? 0)
}
} catch {
print(error)
}
That produces:
New Years Poll
date: January 1, 2019
yays: 454
nays: 139
Caesar's Last Poll
date: March 15, 2019
yays: 42
nays: 0
Set your model as per following format. Also check datatype as per your response.
struct PollsModel:Codable{
var polls : [PollsArrayModel]
enum CodingKeys:String, CodingKey{
case polls
}
struct PollsArrayModel:Codable{
var title : String?
var date : String?
var summary : String?
var stats : PollsStatsModel
enum CodingKeys:String, CodingKey{
case title
case date
case summary
case stats
}
struct PollsStatsModel:Codable{
var total : PollsStatsTotalModel
enum CodingKeys:String, CodingKey{
case total
}
struct PollsStatsTotalModel:Codable{
var dagegen_gestimmt : Int?
var nicht_beteiligt : Int?
var dafür_gestimmt : Int?
var enthalten : Int?
enum CodingKeys:String, CodingKey{
case dagegen_gestimmt = "dagegen gestimmt"
case nicht_beteiligt = "nicht beteiligt"
case dafür_gestimmt = "dafür gestimmt"
case enthalten = "enthalten"
}
}
}
}
}

Parsing arbitrary format JSON date with Swift Decodable

I am attempting to format a date in a JSON document into the format "mm-dd-yyyy". I have the following data:
{"data":[{
"id": 123,
"url": "https://www.google.com",
"title": "The Google link",
"created_at": "2017-08-29T04:00:00.000Z",//date to format
"sent": true,
"alternative": "https://google.com",
"things": [],
"description": [
"search",
"lookup"
],
"company": "Alphabet"
}]}
This is my struct:
struct Sitedata: Decodable{
let data: [site]
}
struct site: Decodable {
let id: Int
let url: String
let title: String
let created_at: String
let sent: Bool
let alternative: String
let things: [String]
let description: [String]
let company: String
}
let sites = try JSONDecoder().decode(Sitedata.self, from: data)
I tried the following method but it produced nil:
func date(dateString: String){
// var dateString = "14.01.2017T14:54:00"
let format = "dd.MM.yyyy'T'HH:mm:ss"
let date = Date()
print("original String with date: \(dateString)")
print("date String() to Date(): \(dateString.toDate(format: format)!)")
print("date String() to formated date String(): \(dateString.toDateString(inputFormat: format, outputFormat: "dd MMMM")!)")
print("format Date(): \(date.toString(format: "dd MMM HH:mm")!)")
}
extension DateFormatter {
convenience init (format: String) {
self.init()
dateFormat = format
locale = Locale.current
}
}
extension String {
func toDate (format: String) -> Date? {
return DateFormatter(format: format).date(from: self)
}
func toDateString (inputFormat: String, outputFormat:String) -> String? {
if let date = toDate(format: inputFormat) {
return DateFormatter(format: outputFormat).string(from: date)
}
return nil
}
}
extension Date {
func toString (format:String) -> String? {
return DateFormatter(format: format).string(from: self)
}
}
How would I be able to parse and then format this date to MM-dd-yyyy?
First, follow the Swift naming convention: UpperCamelCase for class name and lowerCamelCase for variable name. Second, do yourself a favor and make created_at a Date field, like it's clearly is. That will save you a ton of headache later on.
Here's the code:
struct SiteData: Decodable{
let data: [Site]
}
struct Site: Decodable {
let id: Int
let url: String
let title: String
let created_at: Date // changed to Date
let sent: Bool
let alternative: String
let things: [String]
let description: [String]
let company: String
}
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(formatter)
let sites = try decoder.decode(SiteData.self, from: json)
Now that created_at has been parsed from JSON as a proper Date, you can format it however you like:
let formatter2 = DateFormatter()
formatter2.dateFormat = "MM-dd-yyyy"
print(formatter2.string(from: sites.data[0].created_at))
Note that DateFormatter is quite expensive to create and changing its dateFormat property is even more so. If you have to format a lot of dates to strings (or vice versa), create the date formatters only once and keep reusing them.

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()