Coinbase API parsing into Swift App returns incorrect formatting - swift

I am using coinmarketcap api to fetch coin prices using the code down below. The data model Coin is also given below after the code as well as the JSON response. I get an error "The data couldn’t be read because it isn’t in the correct format." What should the correct formating look like?
'''
import Foundation
import SwiftUI
import Alamofire
class CryptoViewModel: ObservableObject {
func fetchData() {
let headers: HTTPHeaders = [
"Accepts": "application/json",
"X-CMC_PRO_API_KEY": "5dd693fc-6446-44c4-8aaa-75b1bfa4376f"
]
AF.request("https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest", headers: headers).response { response in
guard let data = response.data else { return }
do {
let coins = try JSONDecoder().decode([Coin].self, from: data)
print(coins)
}
catch {
print(error.localizedDescription)
}
}
}
}
'''
'''
import SwiftUI
struct Coin: Decodable {
var slug: String?
var symbol: String?
enum CodingKeys: String, CodingKey {
case slug = "slug"
case symbol = "symbol"
}
}
'''
'''
success({
data = (
{
"circulating_supply" = 18697137;
"cmc_rank" = 1;
"date_added" = "2013-04-28T00:00:00.000Z";
id = 1;
"last_updated" = "2021-05-02T14:22:02.000Z";
"max_supply" = 21000000;
name = Bitcoin;
"num_market_pairs" = 9549;
platform = "<null>";
quote = {
USD = {
"last_updated" = "2021-05-02T14:22:02.000Z";
"market_cap" = "1063000586851.752";
"percent_change_1h" = "0.09591311";
"percent_change_24h" = "-1.05109813";
"percent_change_30d" = "-4.45794679";
"percent_change_60d" = "11.80459387";
"percent_change_7d" = "14.06195861";
"percent_change_90d" = "69.54985569999999";
price = "56853.65555441735";
"volume_24h" = "40969975368.50657";
};
};
slug = bitcoin;
symbol = BTC;
tags = (
mineable,
pow,
"sha-256",
"store-of-value",
"state-channels",
"coinbase-ventures-portfolio",
"three-arrows-capital-portfolio",
"polychain-capital-portfolio",
"binance-labs-portfolio",
"arrington-xrp-capital",
"blockchain-capital-portfolio",
"boostvc-portfolio",
"cms-holdings-portfolio",
"dcg-portfolio",
"dragonfly-capital-portfolio",
"electric-capital-portfolio",
"fabric-ventures-portfolio",
"framework-ventures",
"galaxy-digital-portfolio",
"huobi-capital",
"alameda-research-portfolio",
"a16z-portfolio",
"1confirmation-portfolio",
"winklevoss-capital",
"usv-portfolio",
"placeholder-ventures-portfolio",
"pantera-capital-portfolio",
"multicoin-capital-portfolio",
"paradigm-xzy-screener"
);
"total_supply" = 18697137;
}, ...
'''

First, I think you might want to remove the API key in your example and reset it.
Regarding your question. Your response starts with a data property. To parse this you would need start your struct there as well.
So something like this should work;
struct Coins: Decodable {
let data: [Coin]
struct Coin: Decodable {
let symbol: String
let slug: String
let quote: [String: Quote]
}
struct Quote: Decodable {
let price: Double
}
}
I'm nesting the structs to preserve the namespace. Everything is pretty related. If you ever need access to one of them on it's own you could pull them out.
Also you can omit the CodingKeys, since the key is the same as your variable name. Also I don't think you need optionals there, but I'm not completely familiar with the API.
Furthermore I think you get only 1 set of data back and not an array of coins. So I would do the following here;
let coins = try JSONDecoder().decode(Coins.self, from: data)

You are parsing an entire response as an array of coins, but the actual data, that you are interested in, is located under data key, therefore you need to add an additional layer
struct Coins: Decodable {
let coins: [Coin]
}
and decode it instead of Coin
let coins = try JSONDecoder().decode(Coins.self, from: data)

Related

Cannot iterate through array inside array of dictionaries

I'm having trouble parsing a response into workable objects in swift.
Basically, this is the response I get (simplified data for viewing purposes)
"[{\"CO2\":0,\"Places\":[{\"Name\":\"RT(Esc. Sec.)\",\"Code\":\"ST_RT\",\"Type\":0,\"CoordX\":41.176750183105469,\"CoordY\":-8.5490522384643555,\"Provider\":\"ST\",\"Lines\":null},{\"Name\":\"Esc.Sec RT\",\"Code\":\"ST_RT2\",\"Type\":0,\"CoordX\":41.175251007080078,\"CoordY\":-8.54929256439209,\"Provider\":\"ST\",\"Lines\":null},{\"Name\":\"SM\",\"Code\":\"ST_SM\",\"Type\":0,\"CoordX\":41.173740386962891,\"CoordY\":-8.5474367141723633,\"Provider\":\"ST\",\"Lines\":null}],\"Direction\":\"R\"}]"
After I receive the response I do the following:
let dict = try! JSONSerialization.jsonObject(data: responseData!, options: .allowFragments) as? [[String:Any]] ?? [[String:Any]]()
Which results in the following dictionary (I'm sorry for the picture, but I could not take a print screen. Plus those 44 elements are the total number of Places, but I've simplified the response string as I said above)
My problem is, I cannot access each Place dictionary. I've tried iterating through dict["Places"] but that does not seem to work, which I do not understand, given it is an NSArray.
I may be missing something simple, but I can't seem to figure it out.
Any help is welcome.
Thanks!
You can try
// MARK: - Root
struct Root: Codable {
let co2: Int
let places: [Place]
let direction: String
enum CodingKeys: String, CodingKey {
case co2 = "CO2"
case places = "Places"
case direction = "Direction"
}
}
// MARK: - Place
struct Place: Codable {
let name, code: String
let type: Int
let coordX, coordY: Double
let provider: String
let lines: String?
enum CodingKeys: String, CodingKey {
case name = "Name"
case code = "Code"
case type = "Type"
case coordX = "CoordX"
case coordY = "CoordY"
case provider = "Provider"
case lines = "Lines"
}
}
let res = try JSONDecoder().decode([Root].self,from:data)
print(res.places)

Type of expression is ambiguous without more context in Xcode 11

I'm trying to refer to an [Item] list within an #EnvironmentObject however when accessing it within a SwiftUI List, I get the error. What I don't understand is, this error doesn't pop up when following Apple's Landmark tutorial.
As far as I can tell, the [Item] list is loading correctly as I can print it out and do other functions with it. It just bugs out when using it for a SwiftUI List Is there something I've missed?
ItemHome.swift:
struct ItemHome : View {
#EnvironmentObject var dataBank: DataBank
var body: some View {
List {
ForEach(dataBank.itemList) { item in
Text("\(item.name)") // Type of expression is ambiguous without more context
}
}
}
}
Supporting code below:
Item Struct:
struct Item {
var id: Int
var uid: String
var company: String
var item_class: String
var name: String
var stock: Int
var average_cost: Decimal
var otc_price: Decimal
var dealer_price: Decimal
var ctc_price: Decimal
}
DataBank.swift:
final class DataBank : BindableObject {
let didChange = PassthroughSubject<DataBank, Never>()
var itemList: [Item] = load("itemsResults.json") {
didSet {
didChange.send(self)
}
}
}
func load<T: Decodable>(_ filename: String, as type: T.Type = T.self) -> T {
let data: Data
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}
itemsResults.json:
[
{
"id": 1,
"uid": "a019bf6c-44a2-11e9-9121-4ccc6afe39a1",
"company": "Bioseed",
"item_class": "Seeds",
"name": "9909",
"stock": 0,
"average_cost": 0.0,
"otc_price": 0.0,
"dealer_price": 0.0,
"ctc_price": 0.0
},
{
"id": 2,
"uid": "a019bf71-44a2-11e9-9121-4ccc6afe39a1",
"company": "Pioneer",
"item_class": "Seeds",
"name": "4124YR",
"stock": 0,
"average_cost": 0.0,
"otc_price": 0.0,
"dealer_price": 0.0,
"ctc_price": 0.0
}
]
Apparently I missed making sure my models (Item in this case) conformed to the Identifiable protocol fixed it. Still, I wish Apple was more clear with their error messages.
As you mentioned in your answer, a ForEach needs a list of Identifiable objects. If you don't want to make your object implement that protocol (or can't for some reason), however, here's a trick:
item.identifiedBy(\.self)
I had the same problem and it wasn't something related to the line itself, it was related to the curly braces/brackets, so that if someone faced the same problem and doesn't know where the problem is, try to trace the curly braces and the brackets
To conform to Identifiable, just give the id or uid variable a unique value.
An easy way to do this is this:
var uid = UUID()
So your full struct would be:
struct Item: Identifiable {
var id: Int
var uid = UUID()
var company: String
var item_class: String
var name: String
var stock: Int
var average_cost: Decimal
var otc_price: Decimal
var dealer_price: Decimal
var ctc_price: Decimal
}
Xcode may show this error in many cases. Usually when using higher order functions (like map) and reason may be "anything".
There are two types:
Error is in higher order function. In this case your only option is to carefully read the block of code. Once again there may be different kinds of problems.
In some cases higher order function does not have any problem by itself, but there may be a compile time error in function that's called from the body of the higher order function.
Unfortunately Xcode does not point to this error sometimes.
To detect such errors and save lot of time, easy workaround is to temporarily comment higher order function and try to build. Now Xcode will show this error function and will show more reasonable error.

Swift Codable:Nested Dictionary not being read

I am getting some json like so (this is pseudo, not all keys are here):
{
"absolute_magnitude_h" = "23.4";
...
"close_approach_data" = (
{
"close_approach_date" = "1994-09-03";
"epoch_date_close_approach" = 778575600000;
"orbiting_body" = Earth;
}
I have an object with this struct:
struct NEOObj:Codable {
var absoluteMagnitudeH:Float
var designation:String
var isPotentiallyHazardousAsteroid:Bool
var isSentryObject:Bool
var name:String
var nasaJPLURL:String
var neoReferenceID:String
var closeApproachData:[NEOCloseApproachData] = [NEOCloseApproachData]()
private enum CodingKeys: String, CodingKey {
case absoluteMagnitudeH = "absolute_magnitude_h"
case designation = "designation"
case isPotentiallyHazardousAsteroid = "is_potentially_hazardous_asteroid"
case isSentryObject = "is_sentry_object"
case name = "name"
case nasaJPLURL = "nasa_jpl_url"
case neoReferenceID = "neo_reference_id"
}
enum CloseApproachCodingKeys: String, CodingKey {
case closeApproachDate = "close_approach_date"
case epochDateCloseApproach = "epoch_date_close_approach"
case orbitingBody = "orbiting_body"
}
struct NEOCloseApproachData:Codable {
var closeApproachDate:Date
var epochDateCloseApproach:Date
var orbitingBody:String
enum CodingKeys: String, CodingKey {
case closeApproachDate = "close_approach_date"
case epochDateCloseApproach = "epoch_date_close_approach"
case orbitingBody = "orbiting_body"
}
}
and in my file I have this code:
if let arrNEOs = dictJSON["near_earth_objects"] as? Array<Any> {
for thisNEODict in arrNEOs {
do {
let jsonData = try JSONSerialization.data(withJSONObject: thisNEODict, options: .prettyPrinted)
let thisNEOObj = try? JSONDecoder().decode(NEOObj.self, from: jsonData)
print(thisNEOObj!.closeApproachData)
} catch {
}
}
}
But closeApproachData never gets populated. What am I doing wrong?
There are multiple things that would need to be changed for it to work.
closeApproachData should probably be of type [NEOCloseApproachData]. You haven't included CloseApproachCodingKeys in your code but it probably isn't right.
NEOObj.CodingKeys needs to have a case for closeApproachData which would look like
case closeApproachData = "close_approach_data"
NEOCloseApproachData needs a custom decoder to work with the dates for closeApproachDate and epochDateCloseApproach. Since the JSON has different formats for each (String and Int respectively), you can't use on the JSONDecoder.dateDecodingStrategy since it will apply to all dates.
The reason why after decoding you get closeApproachData property empty is the lack of the regarding key for that property into your CodingKeys enum.
Regarding to the Apple doc, it tells next:
Omit properties from the CodingKeys enumeration if they won't be
present when decoding instances, or if certain properties shouldn't be
included in an encoded representation.
That means that generated implementation of decode method for your NEOObj class omit decoding implementation for the closeApproachData property.
Here's a link at the Apple documentation about Encoding and Decoding Custom Types. You can find more details at the "Choose Properties to Encode and Decode Using Coding Keys" paragraph.
Also your custom class NEOCloseApproachData should be conformed to the Coding protocol.

How to create nested dictionary elements in Swift?

I want to create a variable which stores this:
["messageCode": API_200, "data": {
activities = (
{
action = 1;
state = 1;
}
);
messages = (
{
body = hi;
// ...
}
);
}, "message": ]
What I have done is this:
var fullDict: Dictionary<String, AnyObject> = [:]
fullDict["messageCode"] = "API_200" as AnyObject
var data: Dictionary<String, AnyObject> = [:]
fullDict ["data"] = data as AnyObject
Is this way is correct and how I can add activities?
I would suggest to go with creating a custom Model:
struct Model {
var messageCode: String
var data: MyData
var message: String
}
struct MyData {
let activities: [Activity]
let messages: [Message]
}
struct Activity {
var action: Int
var state: Int
}
struct Message {
var body: String
// ...
}
Thus you could use it as:
let data = MyData(activities: [Activity(action: 1, state: 1)], messages: [Message(body: "hi")])
let myModel = Model(messageCode: "API_200", data: data, message: "")
However, if you -for some reason- have to declare it as a dictionary, it could be something like this:
let myDict: [String: Any] = [
"messageCode": "API_200",
"data": ["activities": [["action": 1, "state": 1]],
"messages": [["body": "hi"]]
],
"message": ""
]
which means that myDict is a dictionary contains:
messageCode string.
data as nested dictionary, which contains:
activities array of dictionaries (array of [String: Int]).
messages array of dictionaries (array of [String: String]).
message string.
One of the simplest reasons why you should go with the modeling approach is because when it comes to read from myModel, all you have to do is to use the dot . notation. Unlike working with it as a dictionary, you would have to case its values which could be a headache for some point. For instance, let's say that we want to access the first message body in data messages array:
Model:
myModel.data.messages.first?.body
Dictionary:
if let data = myDict["data"] as? [String: [[String: Any]]],
let messages = data["messages"] as? [[String: String]],
let body = messages.first?["body"] {
print(body)
}
Since you explicitly want it as [String:AnyObject]:
var dict: [String:AnyObject] = ["messageCode":"API_200" as AnyObject,
"data": ["activities": [["action":1,
"state":1]],
"messages": [["body":"hi"]]] as AnyObject,
"message": "" as AnyObject]
Basically all the root values should be typecasted as AnyObject
Or the long way:
//Activities is as Array of dictionary with Int values
var activities = [[String:Int]]()
activities.append(["action": 1,
"state": 1])
//Messages is an Array of string
var messages = [[String:String]]()
messages.append(["body" : "hi"])
//Data is dictionary containing activities and messages
var data = [String:Any]()
data["activities"] = activities
data["messages"] = messages
//Finally your base dictionary
var dict = [String:AnyObject]()
dict["messageCode"] = "API_200" as AnyObject
dict["data"] = data as AnyObject
dict["message"] = "" as AnyObject
print(dict)
Parsing this to get your data back will be hell; with all the type casts and all.
Example (lets capture action):
let action = ((dict["data"] as? [String:Any])?["activities"] as? [String:Int])?.first?.value
As you can see you need to typecast at every level. This is the problem with using dictionaries in Swift. Too much cruft.
Sure, you could use a third-party library like SwiftyJSON to reduce the above to:
let action = dict["data"]["activities"][0]["action"]
But do you want a dependency just for something as simple as this?
Instead...
If your structure is defined then create models instead; as Ahmad F's answer suggests. It will be more readable, maintainable and flexible.
...but since you asked, this is how one would do it with pure Dictionary elements.

Should a dictionary be converted to a class or struct in Swift?

I am working on a native iOS application that receives data in JSON format from a web-service which we are also in control of. The plan is to change out the backend database in a bout 18 months in favor of a different platform.
With that in mind, we want to be sure that that iOS app is going to be relatively easy to adapt to the new datasource, particularly as we may change the keys used in the associative array received from the server via JSON.
There are two goals:
Create a single location for each PHP request where the keys can be modified if needed. This would avoid digging through code to find things like job["jobNumber"].
Clean up our existing code to also eliminate references like job["jobNumber"].
We are both very new to Swift with no Objective-C experience, but I am was thinking a Struct or Class would be appropriate to create references like job.jobNumber.
Should a dictionary be converted into a class or struct? Sample code representing a reusable method of taking a Dictionary<String, String> as shown below and converting it to the recommended type would be extremely helpful.
Example Dictionary:
job = {
"jobNumber" : "1234",
"jobName" : "Awards Ceremony",
"client" : "ACME Productions"
}
Desired result:
println("job name is \(job.name)")
// prints: job name is Awards Ceremony
To access it like this you need to convert your dictionary to Struct as follow:
edit/update: Swift 3.x
struct Job: CustomStringConvertible {
let number: Int
let name, client: String
init(dictionary: [String: Any]) {
self.number = dictionary["jobNumber"] as? Int ?? 0
self.name = dictionary["jobName"] as? String ?? ""
self.client = dictionary["client"] as? String ?? ""
}
var description: String {
return "Job#: " + String(number) + " - name: " + name + " - client: " + client
}
}
let dict: [String: Any] = ["jobNumber": 1234,
"jobName" : "Awards Ceremony",
"client" : "ACME Productions"]
let job = Job(dictionary: dict)
print(job.number) // 1234
print(job.name) // "Awards Ceremony"
print(job.client) // "ACME Productions"
print(job) // "Job#: 1234 - name: Awards Ceremony - client: ACME Productions"""
edit/update:
Swift 4 or later you can use JSON Codable protocol:
struct Job {
let number: Int
let name, client: String
}
extension Job: Codable {
init(dictionary: [String: Any]) throws {
self = try JSONDecoder().decode(Job.self, from: JSONSerialization.data(withJSONObject: dictionary))
}
private enum CodingKeys: String, CodingKey {
case number = "jobNumber", name = "jobName", client
}
}
extension Job: CustomStringConvertible {
var description: String {
return "Job#: " + String(number) + " - name: " + name + " - client: " + client
}
}
let dict: [String: Any] = ["jobNumber": 1234,
"jobName" : "Awards Ceremony",
"client" : "ACME Productions"]
do {
let job = try Job(dictionary: dict)
print(job.number) // 1234
print(job.name) // "Awards Ceremony"
print(job.client) // "ACME Productions"
print(job) // "Job#: 1234 - name: Awards Ceremony - client: ACME Productions\n"
} catch {
print(error)
}
Definitely a job for a struct.
1. Structs are thread-safe and don't need to be managed by ARC.
2. Some studies have found them to be about 30,000x faster to work with than classes in general.
3. Structs also provide default initializers so your code will be cleaner.
4. In this case, you don't have to worry about inheritance/subclassing.
5. The Protocol Oriented Programming paradigm recommends using structs over classes if you're able.
struct Job {
let number: Int
let name: String
let client: String
}
Initializer for free:
let newJob = Job(number: 2, name: "Honey", client: "Jeff")
Or you can create a custom initializer that takes the dictionary:
struct Job {
let number: Int
let name: String
let client: String
init(json: [String: Any]) {
self.number = Int(dictionary["jobNumber"] as? String) ?? 0
self.name = dictionary["jobName"] as? String ?? ""
self.client = dictionary["client"] as? String ?? ""
}
}
usage:
let newJob = Job(json: yourDictionary)
print(newJob.number)
// outputs: 1234
You can add an extension to Dictionary like this to get generic objects:
extension Dictionary where Key == String, Value: Any {
func object<T: Decodable>() -> T? {
if let data = try? JSONSerialization.data(withJSONObject: self, options: []) {
return try? JSONDecoder().decode(T.self, from: data)
} else {
return nil
}
}
}
and use like this on any [String: Any] dictionaries:
let object: MyDecodableStruct? = dictionary.object()
I generally make use of value classes to achieve what you want to do. In my project I do something like following:
protocol Request : class {
func toDictionary() -> [String : String]
}
protocol Response : class {
init(dictionary: [String : String])
}
class MyRequest : Request {
var var1: Int
var var2: String
//Other stuff in class...
func toDictionary() -> [String : String] {
//Convert the value to dictionary and return
}
}
class MyResponse : Response {
var var1: String
var var2: Int
//You could have nested object as members
var innerObject: MyInnerResponseClass
//Other stuff in class...
var someCalculatedProperty: String {
return //Calculate property value
}
required init(dictionary: [String : String]) {
//Initialize class from dictionary
}
}
class MyInnerResponseClass: Response {
//Class definition here...
}
For objects that could be used as request and response you could implement both of the protocols.
You need to write code for translation once, but then it could be easy to use everywhere. Also by using calculated properties you may get extra ease.
I am not sure if you could just do it out of the box in Swift. I will require reflection which is not yet very well supported by Swift. Also even if there is reflection and you come up with clever way to use to achieve what you need, it could be quite slow if the data is quite large.
My two cents about "logic". )all correct about using structs and so on...)
Do NOT keep (as many from web do..) data in dict or JSON, convert it to struct always.
a lot of efficiently, think about for example about sorting in a tableview..