I have JSON like this:
{
"success": true,
"message": "",
"result": {
"buy": [
{
"Quantity": 0.0056,
"Rate": 18527
},
{
"Quantity": 0.11431426,
"Rate": 18526
}
],
"sell":[
{
"Quantity": 8.20604116,
"Rate": 18540
},
{
"Quantity": 0.95600491,
"Rate": 18574.99999998
}
]
}
}
and another set of JSON like this:
{
"lastUpdateId": 1027024,
"bids": [
[
"4.00000000", // PRICE
"431.00000000", // QTY
[] // Can be ignored
]
],
"asks": [
[
"4.00000200",
"12.00000000",
[]
]
]
}
What is the best way to parse these two responses using codable. They both need to be parsed using the same struct or need to be converted to the same struct (whatever will do the job faster). I don't want to create a struct for the entire first response because I am not going to use keys like "success" and "message". I basically want to ignore those and get directly to the "result" key But in the second response, I will being using all the data so I have created a struct for that called TotalOrderBook. What is the best way to do this?
What is confusing me is ignoring the keys "success" and "message" in the first JSON response and getting straight to the value for the key "result". Is it possible to do that without creating an additional struct?
This is what I have right now. I would like to avoid adding another struct since the only thing I really need is the values under buy/bid and sell/sell.
struct TotalOrderBook:Codable{
var buy:[UniversalOrder]?
var sell:[UniversalOrder]?
var bid:[UniversalOrder]?
var ask:[UniversalOrder]?
var buyOrderBook: [UniversalOrder] {
return bid ?? buy ?? [UniversalOrder]()
}
var sellOrderBook: [UniversalOrder] {
return ask ?? sell ?? [UniversalOrder]()
}
var updatedTime:Date
}
struct UniversalOrder:Codable{
var price : Double {
return Double(rate ?? binPrice ?? 0)
}
var size : Double {
return Double(quantity ?? binQuantity ?? 0 )
}
//let numOrders : Int
var quantity:Double?
var rate:Double?
var binPrice:Double?
var binQuantity:Double?
private enum CodingKeys: String, CodingKey {
case rate = "Rate"
case quantity = "Quantity"
//case numOrders, binPrice,
}
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
binPrice = Double(try container.decode(String.self)) ?? nil
binQuantity = Double(try container.decode(String.self)) ?? nil
quantity = nil
rate = nil
}
}
This is how I am decoding:
let decoder = JSONDecoder()
let data = try! JSONSerialization.data(withJSONObject: value) //value is the response from Alamofire
var theWholeOrderBook:UniversalOrder!
do {
theWholeOrderBook = try decoder.decode(UniversalOrder.self, from: data)
} catch let error {
//print ("error is \(e) ** \(value)")
}
To answer your questions directly, yes it is very easy to ignore the success and message key-value pairs and head straight to results.
Despite this it will be a bit complicated to have a single struct to parse both of these JSON responses.
Both of them have a very different structure which will make it easier to use two different structs to use encoding. To highlight some differences :
buy, sell are nested inside results. bids, asks aren't.
The keys are completely different.
buy, sell have an array of key-value pairs while bids, asks simple have an array of values.
Codable structs should be simple and clear. It's better to have two of those corresponding to each response.
Related
So, let me detail a bit with an example:
Lets say I want to get data regarding Persons. Every person has 3 collections: Friends, Family and Work.
Friends collection contains: Person objects(which describes every friend: name age a.s.o.)
Family collection contains: Person objects(which describes every family member: name age a.s.o.)
Work collection contains: Person objects(which describes every work partner: name age a.s.o.)
So it will be something like this:
Me {
age:25,
name: Dan,
Friends [{age:21,name:Andrew}, {age:30,name:Mars}]
Family [{age:21,name:Andrew}, {age:30,name:Mars}]
Work [{age:21,name:Andrew}, {age:30,name:Mars}]
}
Now, having this example in mind. If I don't want to specify my name, the instead of "age:25" I will have "age:nil", right? Because age is an Int?, an obtional.
My problem is, when I parse the JSON data, instead of receiving a nil value (from that obtional age:Int?), I receive an empty array of type [Int], so inteased of nil, I receive [].
Now, this is my example:
class FinancialData: Codable {
var maxAge:Int?
var currentPrice:CurrentPrice?
var targetHighPrice:TargetHighPrice?
var targetLowPrice:TargetLowPrice?
var targetMeanPrice:TargetMeanPrice?
var targetMedianPrice:TargetMedianPrice?
var recommendationMean:RecommendationMean?
var recommendationKey, financialCurrency:String?
var numberOfAnalystOpinions:NumberOfAnalystOpinions?
var totalCash:TotalCash?
var totalCashPerShare:TotalCashPerShare?
var ebitda:Ebitda
var totalDebt:TotalDebt?
var quickRatio:QuickRatio?
var currentRatio:CurrentRatio?
var totalRevenue:TotalRevenue?
var debtToEquity:DebtToEquity?
var revenuePerShare:RevenuePerShare?
var returnOnAssets:ReturnOnAssets?
var returnOnEquity:ReturnOnEquity?
var grossProfits:GrossProfits?
var freeCashflow:FreeCashFlow?
var operatingCashflow:OperatingCashFlow?
// var earningsGrowth:EarningsGrowth?
var revenueGrowth:RevenueGrowth?
var grossMargins:GrossMargins?
var ebitdaMargins:EbitdaMargins?
var operatingMargins:OperatingMargins?
var profitMargins:ProfitMargins?
enum CodingKeys: String, CodingKey {
case maxAge = "maxAge"
case currentPrice = "currentPrice"
case targetHighPrice = "targetHighPrice"
case targetLowPrice = "targetLowPrice"
case targetMeanPrice = "targetMeanPrice"
case targetMedianPrice = "targetMedianPrice"
case recommendationMean = "recommendationMean"
case recommendationKey = "recommendationKey"
case numberOfAnalystOpinions = "numberOfAnalystOpinions"
case totalCash = "totalCash"
case totalCashPerShare = "totalCashPerShare"
case ebitda = "ebitda"
case totalDebt = "totalDebt"
case quickRatio = "quickRatio"
case currentRatio = "currentRatio"
case totalRevenue = "totalRevenue"
case debtToEquity = "debtToEquity"
case revenuePerShare = "revenuePerShare"
case returnOnAssets = "returnOnAssets"
case returnOnEquity = "returnOnEquity"
case grossProfits = "grossProfits"
case freeCashflow = "freeCashflow"
case operatingCashflow = "operatingCashflow"
// case earningsGrowth = "earningsGrowth"
case revenueGrowth = "revenueGrowth"
case grossMargins = "grossMargins"
case ebitdaMargins = "ebitdaMargins"
case operatingMargins = "operatingMargins"
case profitMargins = "profitMargins"
case financialCurrency = "financialCurrency"
}
}
//....//
class Ebitda: Codable {
var raw:Double?
var fmt, longFmt: String?
enum CodingKeys: String, CodingKey {
case raw = "raw"
case fmt = "fmt"
case longFmt = "longFmt"
}
}
Ebitda is the problem, is a class. If there is no Ebitda class at my API, I am not receiving an Obtional nil value of Ebitda?, I am receiving an empty array of Ebitda, and my program crashes. I can't modify Ebitda? to [Ebitda] because when the value is NOT nil, I receive an Ebitda object, not an array of Ebitda objects.
So, this is the error when Ebitda is nil, but I receive an empty array instead of nil:
typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "financialData", intValue: nil), CodingKeys(stringValue: "ebitda", intValue: nil)], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil)).
I will attach a short description of the JSON data I import, Look for ebitda:
{
"financialData": {
"maxAge": 86400,
"recommendationKey": "buy",
"numberOfAnalystOpinions": {
"raw": 23,
"fmt": "23",
"longFmt": "23"
},
"totalCash": {
"raw": 848978968576,
"fmt": "848.98B",
"longFmt": "848,978,968,576"
},
"totalCashPerShare": {
"raw": 106.165,
"fmt": "106.17"
},
"ebitda": [],
"totalDebt": {
"raw": 543510003712,
"fmt": "543.51B",
"longFmt": "543,510,003,712"
},
"quickRatio": [],
"currentRatio": [],
"debtToEquity": [],
"revenuePerShare": {
"raw": 11.389,
"fmt": "11.39"
},
"returnOnAssets": {
"raw": 0.00885,
"fmt": "0.88%"
},
"returnOnEquity": {
"raw": 0.101339996,
"fmt": "10.13%"
},
"grossProfits": {
"raw": 92407000000,
"fmt": "92.41B",
"longFmt": "92,407,000,000"
},
"freeCashflow": [],
"operatingCashflow": [],
"earningsGrowth": {
"raw": 0.069,
"fmt": "6.90%"
},
"revenueGrowth": {
"raw": 0.04,
"fmt": "4.00%"
},
"grossMargins": {
"raw": 0,
"fmt": "0.00%"
},
"ebitdaMargins": {
"raw": 0,
"fmt": "0.00%"
},
"operatingMargins": {
"raw": 0.33514,
"fmt": "33.51%"
},
"profitMargins": {
"raw": 0.29790002,
"fmt": "29.79%"
},
"financialCurrency": "USD"
}
}
As you can see, also quickRatio and currentRatio are empty arrays of no type instead of CurrentRatio object nil. What can I do to solve this issue? Thank you!
I was thinking about computed properties, but it doesn't work. Is there any way to detect when parsing the data if the value is nil or not before attributing it to my variable? If I can do that, I will be able (for ebitda and similar cases):
If the value is not nil (in my case is not an empty array) to atribute to ebitda variable an Ebitda object from the json data.
If the value is nil(in my case an empty array of no type) to atribute to ebitda value a nil value of Ebitda? object.
I think that is the solution, I can't atribute to ebitda:Ebitda? an array.. I have to atribute a nil value of Ebitda?...but how can I detect that before atributing the value?
Thank you and I am here at any hour!
I have seen this issue before. I believe this is due to JS backend, which sometimes (and I don't really know why) sends empty array instead of the null. To distill your problem in simpler case: seems that you may get 2 different values for the same field:
let json1 = """
{
"age": 2
}
""".data(using: .utf8)!
or, if value is nil, you are getting an empty array:
let json2 = """
{
"age": []
}
""".data(using: .utf8)!
So if you just define your age as Int:
struct Obj: Codable {
let age: Int?
}
it will work for json1, but will crash for json2.
Unfortunately it means you need to switch to "manually parse" such field, and because of it, the entire structure:
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Keys.self)
age = try? container.decode(Int.self, forKey: .age)
// parse every field in the structure
}
But if you have many fields in your struct, it becomes very tedious. So a bit of a shortcut is to define a structure that parses it for you:
struct ValueOrEmptyArray<T: Codable>: Codable {
let value: T?
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
value = try container.decode(T.self) // try to parse
} catch {
value = nil // failed parsing - assume it's nil
}
}
}
And so you can use it in your struct without doing parsing for each and every field:
struct Obj: Codable {
let age: ValueOrEmptyArray<Int>
}
Of course it also means that to access a value of such field, you need to access age.value:
let decoded1 = try JSONDecoder().decode(Obj.self, from: json1)
print(decoded1.age.value) // 2
let decoded2 = try JSONDecoder().decode(Obj.self, from: json2) // throws an exception
print(decoded2.age.value) // nil
I am using a third-party API to get data. It is a rather complex payload but I'm experiencing a problem with one return. For this example I'm over-simplifying the structure. This structure actually has 53 entries, 34 of which are structures themselves.
struct MlsItemData: Codable, Hashable {
let mls_id: String
let photos: [MlsItemPhoto]?
let features: [MlsItemFeature]?
let address: MlsItemAddress
let move_in_date: String?
let stories: Int?
let client_flags: MlsItemClientFlags?
let tax_history: [MlsItemTaxHistory]? <-- our propblem child
let new_construction: Bool?
let primary: Bool?
let prop_common: MlsItemPropertyCommon?
There are a whole load of other data objects in this API's results but I'm focusing on one item with the label tax_history. When there is data to be shared the key contains an Array like below.
{
"tax_history": [
{
"assessment": {
"building": null,
"total": 3900,
"land": null
},
"tax": 683,
"year": "2020"
},
{
"assessment": {
"building": null,
"total": 4093,
"land": null
},
"tax": 698,
"year": 2019
}
]
}
When the API has no data to share I was expecting:
"tax_history": [ ]
or
"tax_history": null
or just not in the payload at all. But instead the API is sending:
"tax_history": { }
I'm having difficulty as to how to deal with this in the decoder. Obviously, the built in decoder returns the "Expected to decode Array but found a dictionary instead", but is there a simple way to write a custom decoder for "just" the tax_history key and how would it be written for either getting an Array or an empty dictionary?
Yes, it is possible to decode this unusual payload using JSONDecoder. One way to do so is to use a custom type to represent either the empty or non-empty scenarios, and implement a custom initializer function and attempt to decode both cases to see which one works:
struct TaxHistoryItem: Decodable {
let year: String
// ...
}
enum TaxHistory: Decodable {
case empty
case items([TaxHistoryItem])
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let items = try? container.decode([TaxHistoryItem].self) {
self = .items(items)
} else {
struct EmptyObject: Decodable {}
// Ignore the result. We just want to verify that the empty object exists
// and doesn't throw an error here.
try container.decode(EmptyObject.self)
self = .empty
}
}
}
You could create a specific type that holds this array and then write a custom init(from:) for it.
In the init we try to decode the json as an array and if it fails we simply assign an empty array to the property (nil for an optional property is another possible solution but I prefer an empty collection before nil)
struct TaxHistoryList: Codable {
let history: [TaxHistory]
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let list = try? container.decode([TaxHistory].self) {
history = list
} else {
history = []
}
}
}
struct TaxHistory: Codable {
let tax: Int
let year: String
// other stuff
}
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.
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) }
I'm trying to decode the following JSON Object
{
"result":[
{
"rank":12,
"user":{
"name":"bob","age":12
}
},
{
"1":[
{
"name":"bob","age":12
},
{
"name":"tim","age":13
},
{
"name":"tony","age":12
},
{
"name":"greg","age":13
}
]
}
]
}
struct userObject {
var name: String
var age: Int
}
Basically a JSON Array with two different object types
{ "rank":12, "user": {userObject} }
and a
"1" : array of [userObjects]
struct data: Decodable {
rank: Int
user: user
1: [user] <-- this is one area Im stuck
}
Thanks in advance
Just for fun:
First you need structs for the users and the representation of the first and second dictionary in the result array. The key "1" is mapped to one
struct User : Decodable {
let name : String
let age : Int
}
struct FirstDictionary : Decodable {
let rank : Int
let user : User
}
struct SecondDictionary : Decodable {
let one : [User]
private enum CodingKeys: String, CodingKey { case one = "1" }
}
Now comes the tricky part:
First get the root container.
Get the container for result as nestedUnkeyedContainer because the object is an array.
Decode the first dictionary and copy the values.
Decode the second dictionary and copy the values.
struct UserData: Decodable {
let rank : Int
let user : User
let oneUsers : [User]
private enum CodingKeys: String, CodingKey { case result }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
var arrayContainer = try container.nestedUnkeyedContainer(forKey: .result)
let firstDictionary = try arrayContainer.decode(FirstDictionary.self)
rank = firstDictionary.rank
user = firstDictionary.user
let secondDictionary = try arrayContainer.decode(SecondDictionary.self)
oneUsers = secondDictionary.one
}
}
If this code is preferable over traditional manual JSONSerialization is another question.
If your JSON format is given then you are pretty much out of luck, since you will most likely have to parse your array as [Any] which is, to put it mildly, not very useful. If on the other hand you are able to modify the format of the JSON you should start from the other direction. Define your desired Swift object and encode it using JSONEncoder.encode(...) in order to quickly determine how your JSON should look like in order to make it parse in as typed a way as possible.
This approach will easily half your JSON handling code as your web service protocol will end up being structured much better. This will likely improve the structure of the overall system since it will yield a much more stable communication protocol.
Sadly enough this approach is not always possible which is when things get messy. Given your example you will be able to parse your code as
let st = """
{
"result":[
{
"rank":12,
"user":{
"name":"bob",
"age":12
}
},
{
"1":[
{
"name":"bob","age":12
},
{
"name":"tim","age":13
},
{
"name":"tony","age":12
},
{
"name":"greg","age":13
}
]
}
]
}
"""
let jsonData1 = st.data(using: .utf8)!
let arbitrary = try JSONSerialization.jsonObject(with: jsonData1, options: .mutableContainers)
This will let you access your data with a bunch of casts as in
let dict = arbitrary as! NSDictionary
print(dict["result"])
you get the idea. not very useful as you would very much like to use the Codable protocol as in
struct ArrayRes : Codable {
let result : [[String:Any]]
}
let decoder1 = JSONDecoder()
do {
let addrRes = try decoder.decode(ArrayRes.self, from: jsonData1)
print(addrRes)
} catch {
print("error on decode: \(error.localizedDescription)")
}
Unfortunately this does not work since Any is not Codable for slightly obvious reasons.
I hope you are able to change your JSON protocol since the current one will be the root cause of lot of messy code.