Using bs-json to Decode object with dynamic keys in root - reason

I am trying to decode the following JSON object into a Reason object.
{"AAPL":{"price":217.36}}
The key in the root of the object is dynamic.
The following general example works when the key is not in the root. How would I change it so it works for a dynamic key in the root?
module Decode = {
let obj = json =>
Json.Decode.{
static: json |> field("static",string),
dynamics: json |> field("dynamics", dict(int)),
};
};

If your data looks like:
let data = {| {
"AAPL": { "price": 217.36 },
"ABCD": { "price": 240.5 }
} |};
You can get a Js.Dict with the following:
module Decode = {
open Json.Decode;
let price = field("price", float);
let obj = dict(price);
};
let decodedData = data |> Json.parseOrRaise |> Decode.obj;
let _ = decodedData->(Js.Dict.unsafeGet("AAPL")) |> Js.log;
It should print 217.36

Related

Coinbase API parsing into Swift App returns incorrect formatting

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)

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.

How to access nested dictionary?

I can print this in the debugger:
(lldb) print params["message"]!
([String : String]) $R5 = 2 key/value pairs {
[0] = (key = "body", value = "iPadUser has started a new stream")
[1] = (key = "title", value = "Stream started")
}
But I am trying to figure out how to access the body and title separately.
I construct params in this way:
let recipients = ["custom_ids":[recips]]
let notificationDetails = "hello there"
let content = [
"title":title,
"body":details
]
let params: [String:Any] = [
"group_id":"stream_requested",
"recipients": recipients,
"message": content
]
print((params["message"] as! [String: Any])["title"] as! String)
You need to cast the Dictionary value as specific type, since the compiler doesn't know what to expect. (Please mind that you mustn't use force unwrap in other way than example code.)
Considering you need to fetch array values when recipients dictionary looks like this:
let recipients = ["custom_ids":["recipe1", "recipe2", "etc"]]
get to the ids like this:
guard let recipients = params["recipients"] as? [String: Any],
let customIDs = recipients["custom_ids"] as? [String]
else { return }
for id in customIDs {
print(id) // Gets your String value
}

Use SwiftyJSON to get proper data

This is my JSON data, how can I get src data in 0 in pickArray?
"pickArray" : "{\"0\":{\"src\":\"https:\/\/fb-s-d-a.akamaihd.net\/h-ak-xpl1\/v\/t1.0-9\/p720x720\/18010403_1525007564199498_8009700960533638318_n.png?oh=25dbc9c1522dcfdd1d15cdd3e8c0c7da&oe=59997685&__gda__=1502470695_f212ade003e9b1c4ddc6a3ab6cc9e7e7\",\"width\":720,\"height\":720}}"
If I do it like this:
let dataArray = json["pickArray"]
print("dataArray = ",dataArray)
dataArray = {"0":{"src":"https://fb-s-d-a.akamaihd.net/h-ak-xpl1/v/t1.0-9/p720x720/18010403_1525007564199498_8009700960533638318_n.png?oh=25dbc9c1522dcfdd1d15cdd3e8c0c7da&oe=59997685&__gda__=1502470695_f212ade003e9b1c4ddc6a3ab6cc9e7e7","width":720,"height":720}}
But if I do it like this, show null:
let srcArray = dataArray["0"]
print("srcArray = ",srcArray)
I'm using swift3.0
Its looks like that with key pickArray you are having JSON response in String so get that string and convert it data and get JSON from it and then get src from it.
let stringResponse = json["pickArray"].stringValue
if let data = stringResponse.data(using: .utf8) {
let pickArray = JSON(data: data)
//Now access the pickArray to get the src
var sortedKeys = [String]()
if let allKeys = pickArray.dictionaryObject {
sortedKeys = Array(allKeys.keys).sorted { $0.compare($1, options: .numeric) == .orderedAscending }
}
for key in sortedKeys {
print(pickArray[key]["src"].stringValue)
print(pickArray[key]["width"].intValue)
print(pickArray[key]["height"].intValue)
}
}
let srcArray = dataArray["0"].dictionaryObject!
print("srcArray = \(srcArray)")
Now you can access element of "0" value as like below. Hope this work for you.
let jsonScr = JSON(srcArray)
let srcURL = jsonScr["scr"].stringValue

Access the JSONString in mapping using Swift ObjectMapper library

I would like to return the native JSON String to store in Realm, since Realm can't store collections of native objects.
Example JSON:
{ "root": { "id":1, "name":"name", "array":["a", "b", "c"] }}
func mapping(map:Map) {
id <- map["id]
name <- map["name"]
array <- map["array"].JSONString // array = "[\"a\", \"b\", \"c\"]"
}
Is this possible?
Same question on github https://github.com/Hearst-DD/ObjectMapper/issues/730
If it helps, Realm can indeed store collections of sub-objects.
class StringObject: Object {
dynamic var string = ""
}
class Root: Object {
dynamic var id = 0
dynamic var name = ""
let array = List<StringObject>()
}
Beyond that, if I understand ObjectMapper correctly, by the time map["array"] is already passed in mapping, it's already been converted from JSON to a Swift object. In which case, you'd need to reserialize it back into JSON yourself.