Which structure to use to parse data in JSON? - json

I am very new to using JSON and I'm trying to figure it out the best solution to parse the below data into my app using swift. So far I've been successfully creating structures to parse the data into my apps but with this API I'm getting a bit confused.
The path of the value i'm looking returns: 0.price
Below the JSON i'm working on - i need a structure to retrieve the 'price' value - i understand it is a swift dictionary nested into an array but I really don't know how to create this structure to get the 0.price value.
[
{
"id": "BTC",
"currency": "BTC",
"symbol": "BTC",
"name": "Bitcoin",
"logo_url": "https://s3.us-east-2.amazonaws.com/nomics-api/static/images/currencies/btc.svg",
"price": "9397.86203481",
"price_date": "2020-07-09T00:00:00Z",
"price_timestamp": "2020-07-09T05:10:00Z",
"circulating_supply": "18427462",
"max_supply": "21000000",
"market_cap": "173178745528",
"rank": "1",
"high": "19343.01808710",
"high_timestamp": "2017-12-16T00:00:00Z",
"1d": {
"volume": "19780482959.23",
"price_change": "55.47211721",
"price_change_pct": "0.0059",
"volume_change": "1974758604.83",
"volume_change_pct": "0.1109",
"market_cap_change": "1030908096.99",
"market_cap_change_pct": "0.0060"
},
"30d": {
"volume": "663105718213.57",
"price_change": "-472.08068928",
"price_change_pct": "-0.0478",
"volume_change": "-451402235448.26",
"volume_change_pct": "-0.4050",
"market_cap_change": "-8416909381.05",
"market_cap_change_pct": "-0.0463"
}
}
]
Thanks,
Max

Use Codable to parse the above JSON response.
Codable Models:
Option-1 : If you want to parse all data
struct Response: Codable {
let id, currency, symbol, name: String
let logoUrl: String
let price: String
let priceDate, priceTimestamp: String
let circulatingSupply, maxSupply, marketCap, rank: String
let high: String
let highTimestamp: String
let the1D, the30D: The1_D
enum CodingKeys: String, CodingKey {
case id, currency, symbol, name, logoUrl, price, priceDate, priceTimestamp, circulatingSupply, maxSupply, marketCap, rank, high, highTimestamp
case the1D = "1d"
case the30D = "30d"
}
}
struct The1_D: Codable {
let volume, priceChange, priceChangePct, volumeChange: String
let volumeChangePct, marketCapChange, marketCapChangePct: String
}
Option-2: In case you just need the price value and ignore everything else,
struct Response: Codable {
let price: String
}
Parse the JSON data like so,
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let response = try decoder.decode([Response].self, from: data)
let price = response.first?.price //Get the value of price here..
} catch {
print(error)
}

Related

Swift - How to extract all values with same key names

having looked at several posts I didn't find what I was looking for. I hope this post will help me.
I use the api of CoinDesk, what I'm trying to do now is to retrieve in the answer all the codes (EUR, USD, GBP) but I can't get all the assets.
{
"time": {
"updated": "Feb 20, 2021 19:48:00 UTC",
"updatedISO": "2021-02-20T19:48:00+00:00",
"updateduk": "Feb 20, 2021 at 19:48 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": "57,014.5954",
"description": "United States Dollar",
"rate_float": 57014.5954
},
"GBP": {
"code": "GBP",
"symbol": "£",
"rate": "40,681.1111",
"description": "British Pound Sterling",
"rate_float": 40681.1111
},
"EUR": {
"code": "EUR",
"symbol": "€",
"rate": "47,048.5582",
"description": "Euro",
"rate_float": 47048.5582
}
}
}
here's how I'm going to get the data
public class NetworkManager {
static public func fetchBPI() {
let url = "https://api.coindesk.com/v1/bpi/currentprice.json"
Alamofire.request(url).responseJSON { response in
switch response.result {
case .success:
print("✅ Success ✅")
if let json = response.data {
do {
let data = try JSON(data: json)
print(data)
let context = PersistentContainer.context
let entity = NSEntityDescription.entity(forEntityName: "BPI", in: context)
let newObject = NSManagedObject(entity: entity!, insertInto: context)
//Date Formatter
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM-dd-yyyy HH:mm"
let date = dateFormatter.date(from: data["time"]["updated"].rawValue as! String)
dateFormatter.timeZone = NSTimeZone.local
let timeStamp = dateFormatter.string(from: date ?? Date())
newObject.setValue(timeStamp, forKey: "time")
newObject.setValue(data["chartName"].rawValue, forKey: "chartName")
newObject.setValue(data["bpi"]["EUR"]["symbol"].rawValue, forKey: "symbol")
newObject.setValue(data["bpi"]["EUR"]["rate"].rawValue, forKey: "rate")
newObject.setValue(data["bpi"]["EUR"]["code"].rawValue, forKey: "code")
do {
try context.save()
print("✅ Data saved ✅")
} catch let error {
print(error)
print("❌ Saving Failed ❌")
}
}
catch {
print("❌ Error ❌")
}
}
case .failure(let error):
print(error)
}
}
}
}
I would like to get in the bpi key all the codes and put them in a list to use them.
The best way to handle the json here in my opinion is to treat the content under "bpi" as a dictionary instead.
struct CoinData: Codable {
let time: Time
let chartName: String
let bpi: [String: BPI]
}
struct BPI: Codable {
let code: String
let rate: Double
enum CodingKeys: String, CodingKey {
case code
case rate = "rate_float"
}
}
struct Time: Codable {
let updated: Date
enum CodingKeys: String, CodingKey {
case updated = "updatedISO"
}
}
I have removed some unnecessary (?) properties and also note that I made updatedISO in Time into a Date if that might be useful since it's so easy to convert it.
To properly decode this use try with a do/catch so you handle errors properly.
Here is an example of that where I also loop over the different currencies/rates
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let result = try decoder.decode(CoinData.self, from: data)
print(result.time.updated)
let coins = result.bpi.values
for coin in coins {
print(coin)
}
} catch {
print(error)
}
Output:
2021-02-20 19:48:00 +0000
BPI(code: "GBP", rate: 40681.1111)
BPI(code: "USD", rate: 57014.5954)
BPI(code: "EUR", rate: 47048.5582)
You should use objects to represent the data from the server. It would be something like this:
struct CoinData: Codable {
let time: Time
let disclaimer, chartName: String
let bpi: BPI
}
struct BPI: Codable {
let usd, gbp, eur: Eur
enum CodingKeys: String, CodingKey {
case usd = "USD"
case gbp = "GBP"
case eur = "EUR"
}
}
struct Eur: Codable {
let code, symbol, rate, eurDescription: String
let rateFloat: Double
enum CodingKeys: String, CodingKey {
case code, symbol, rate
case eurDescription = "description"
case rateFloat = "rate_float"
}
}
struct Time: Codable {
let updated: String
let updatedISO: String
let updateduk: String
}
Than when you download the data you parse it like this:
let coinData = try? JSONDecoder().decode(CoinData.self, from: jsonData)
coinData?.bpi.eur // Access EUR for instance
UPDATE:
Simple demo to demonstrate the parsing using the data you get from your server:
let dataFromServer = "{\"time\":{\"updated\":\"Feb 20, 2021 21:02:00 UTC\",\"updatedISO\":\"2021-02-20T21:02:00+00:00\",\"updateduk\":\"Feb 20, 2021 at 21:02 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\":\"56,689.8367\",\"description\":\"United States Dollar\",\"rate_float\":56689.8367},\"GBP\":{\"code\":\"GBP\",\"symbol\":\"£\",\"rate\":\"40,449.3889\",\"description\":\"British Pound Sterling\",\"rate_float\":40449.3889},\"EUR\":{\"code\":\"EUR\",\"symbol\":\"€\",\"rate\":\"46,780.5666\",\"description\":\"Euro\",\"rate_float\":46780.5666}}}"
do {
let json = try JSONDecoder().decode(CoinData.self, from: dataFromServer.data(using: .utf8)!)
print(json.bpi.eur) // Will print EUR object
} catch let error {
print(error)
}

Swift JSON string to dictionary not able to parse all values

I download a JSON file from my database which returns the following string:
["ingredients": asdasdasd,
"price": 14,
"_id":
{
"$oid" = 5e8e3706f00ca80f251485c3;
},
"category": sadad,
"available": Disponibile,
"name": asdadasd]
I then convert this string to data to then convert it to a Dictionary<String, Any>
if let myData = responseString.data(using: .utf8) {
do {
let myArray = try (JSONSerialization.jsonObject(with: myData) as? [Dictionary<String, Any>])!
completion(myArray, nil)
} catch let error as NSError {
print("error:" + String(describing: error))
completion(nil, error)
}
}
This works perfectly fine, as I can get, let's say, the price parameter doing myArray["price"].
The problem arises when I try to get the Id parameter, as when I do myArray["_id"] I get:
{
"$oid" = 5e8e370af00ca80f251485cf;
}
I would like to directly get the ID parameter, and I can't parse this value to JSON as it is not in JSON format. At the moment I am fixing the issue by manipulating this string replacing the = with :, removing the ; and other nasty stuff, but I am sure there is a more efficient way to solve the issue.
myArray["_id"] is a Dictionary in your myArray.So you have to convert your myArray["_id"] to dictionary and then you can access the id.
try this
let id = (myArray["_id"] as Dictionary ?? [:])["$oid"] as? String ?? ""
What you've posted looks like debugger print output, not JSON from your server. I'm going to assume that your JSON actually looks like this:
[
{
"ingredients": "asdasdasd",
"price": 14,
"_id": {
"$oid": "5e8e3706f00ca80f251485c3"
},
"category": "sadad",
"available": "Disponibile",
"name": "asdadasd"
}
]
Given that, you could use a model struct like
struct Recipe: Codable {
let ingredients: String
let price: Int
let id: ID
let category, available, name: String
enum CodingKeys: String, CodingKey {
case ingredients, price
case id = "_id"
case category, available, name
}
}
struct ID: Codable {
let oid: String
enum CodingKeys: String, CodingKey {
case oid = "$oid"
}
}
typealias Recipes = [Recipe]
to parse it using
do {
let recipes = try JSONDecoder(Recipes.self, from: myData)
let firstOid = recipe.first?.id.oid
} catch {
print(error)
}
That said, I would recommend avoiding generic names like myArray for your variables.
Also, when retrieving JSON data from your server, it's not necessary to first convert them to a String and then back to Data before passing it to the JSON parser - simply pass the raw server data along.

How to access varying JSON key in Swift 4.2 natively?

I have the following dummy JSON data for a bus to school.
{
"toSchool": {
"weekday": [{
"hour": 7,
"min": 10,
"type": null,
"rotary": false
}],
"sat": [{
"hour": 8,
"min": 15,
"type": null,
"rotary": true
}]
}
}
I would like to access "weekday" and "sat" key with a variable based on user input. How can I achieve this natively?
Using SwiftyJSON, it is fairly simple like below
let json = try JSON(data: data)
let userDirection = "shosfc"
let userWeek = "weekday"
let busList = json[userDirection][0][userWeek]
However, I was wondering how this would be done natively to remove dependencies.
It seems that CodingKey and enum might be the way to handle this. When the example is as simple as this, I can understand. However, I just cannot get my head around it for my particular usage where it involves custom objects not just String.
How can I do this? Please help.
This is based on your earlier question
func bus(isWeekday: Bool = true) -> [Bus] {
return isWeekday ? shosfc.weekDay : shosfc.sat
}
I think code that below will work:
struct SampleResponse: Codable {
let toSchool: ToSchool
}
struct ToSchool: Codable {
let weekday, sat: [Sat]
}
struct Sat: Codable {
let hour, min: Int
let type: String?
let rotary: Bool
}
To decode this type of response, you must decode this JSON with SampleResponse type.
let sampleResponse = try? newJSONDecoder().decode(SampleResponse.self, from: jsonData)
After that, you can reach variables like you asked.
You can convert JSON string to Dictionary in swift and access it the same way you just did:
func parseToDictionary(_ jsonStr: String) -> [String: Any]? {
if let data = jsonStr.data(using: .utf8) {
do {
return try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
} catch {
print(error.localizedDescription)
}
}
return nil
}
let jsonStr = "{Your JSON String}"
let json = parseToDictionary(jsonStr)
let userDirection = "shosfc"
let userWeek = "weekday"
let busList = json[userDirection][0][userWeek]

Filtering a JSON array to display multiple or single key values

This block allows me to select a single currency and return all the API values declared in the model. id, symbol, name, price, marketCap and so on.
Interface
let data = rawResponse.data
if let eth = data.filter({ (item) -> Bool in
let cryptocurrency = item.value.symbol
return cryptocurrency == "ETH"
}).first {
print(eth)
}
I need the flexibility to return only a single value such as price. I could comment out all the properties of the struct except for price but that limits the functionality.
I was told I could compare let cryptocurrency = item.value.symbol with return cryptocurrency == "ETH"etc but I am not sure how to do accomplish this.
Model
struct RawServerResponse : Codable {
var data = [String:Base]()
private enum CodingKeys: String, CodingKey {
case data
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let baseDictionary = try container.decode([String:Base].self, forKey: .data)
baseDictionary.forEach { data[$0.1.symbol] = $0.1 }
}
}
struct Base : Codable {
let id : Int?
let name : String?
let symbol : String
let quotes : [String: Quotes]
}
struct Quotes : Codable {
let price : Double?
}
JSON
"data": {
"1027": {
"id": 1027,
"name": "Ethereum",
"symbol": "ETH",
"website_slug": "ethereum",
"rank": 2,
"circulating_supply": 99859856.0,
"total_supply": 99859856.0,
"max_supply": null,
"quotes": {
"USD": {
"price": 604.931,
"volume_24h": 1790070000.0,
"market_cap": 60408322833.0,
"percent_change_1h": -0.09,
"percent_change_24h": -2.07,
"percent_change_7d": 11.92
}
}
To filter an array and display a single value (or in your case find Ethereum and use the price) you can do this:
let ethPrice = rawResponse.data.filter({ $0.value.symbol == "ETH" }).first?.price
You made it too complicated.
if this needs to be dynamic you can place it in a function
func getPrice(symbol: String) -> Double? {
return rawResponse.data.filter({ $0.value.symbol == symbol }).first?.price
}
You need to think of what you are doing in smaller pieces of work.
Get the object that you want which is this part
let item = rawResponse.data.filter({ $0.value.symbol == symbol }).first
Then you have access to all of the properties of that one object.
If you wanted to print the name and price of all items you can do that quite easily also
for item in rawResponse.data {
print("\(item.symbol) - \(item.price)"
}

parsing JSON with a decodable?

I have a JSON file:
{
"name": "Jens",
"time": "11.45",
"date": "2018:04:17",
"differentTimestamps":[""]
"aWholeLotOfnames":{
"name1": "Karl"
"name2": "pär"
}
How to parse above JSON ? I have checked this tutorial https://www.youtube.com/watch?v=YY3bTxgxWss. One text tutorial to but i don't get how to make a variable that can take a
"nameOfVar"{}
If it's not a dictionary. The tutorial are using a var nameOfVar: [what should be here in this case] for one that nearly looks like it. The thing is though that theirs are starting with a [{ and ends with a }] while mine only starts with a {? i don't know how to solve this?
Creating corresponding Swift data types for JSON is very easy.
A dictionary {} can be decoded into a class / struct where the keys become properties / members.
An array [] can be decoded into an array of the given (decodable) type.
Any value in double quotes is String even "12" or "false".
Numeric floating point values are Double, integer values are Int and true / false is Bool
null is nil
let jsonString = """
{
"name": "Jens",
"time": "11.45",
"date": "2018:04:17",
"differentTimestamps":[""],
"aWholeLotOfnames":{
"name1": "Karl",
"name2": "pär"
}
}
"""
struct Item: Decodable {
let name, time, date: String
let differentTimestamps: [String]
let aWholeLotOfnames: AWholeLotOfnames
}
struct AWholeLotOfnames : Decodable {
let name1, name2 : String
}
let data = Data(jsonString.utf8)
do {
let result = try JSONDecoder().decode(Item.self, from: data)
print(result)
} catch { print(error) }