parsing JSON with a decodable? - json

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

Related

I expect the result to be a dictionary but it is accessed like a property... why?

I have this JSON coming from a server...
{
"cars": [
{
"name": "Ferrari",
"price": "100"
},
{
"name": "Lamborghini",
"price": "200"
},
{
"name": "Ford Pinto",
"price": "1"
}
]
}
This JSON is a dictionary called cars that contains an array of cars, right?
Then I have this struct...
struct Cars: Codable {
let cars: [Car]
}
struct Car: Codable, Hashable, Identifiable {
let id = UUID()
let name: String
let price: String
}
and I decode the JSON using this:
let (data, _) = try await urlSession.data(from: url)
let result = try JSONDecoder().decode(Cars.self, from: data)
let listOfCars = result.cars
This is something I don't understand.
in result.cars, cars is a property of result that was declared as an array in the struct Cars. Not a dictionary.
I was expecting to access it using result["cars"].
Why is that?
In your code here...
let (data, _) = try await urlSession.data(from: url)
let result = try JSONDecoder().decode(Cars.self, from: data)
let listOfCars = result.cars
result is an instance of the Struct Cars. Your set up code has told Swift how to translate from a JSON dictionary into your own Struct.
So everything inside of result is accessed just like how you would access it in the following...
let result = Cars(cars: [
Car(name: "Ford", price: "£10,000")
])
print(result.cars)
The only difference is how you are creating it. Instead of using the init method like this you are using a JSON decode to decode some JSON into your custom type.
As said in the comments and answers, it takes a result type according to your decode strategy. In your code result type is Cars not a dictionary. So you access the properties with using result.cars
If you want something as dictionary instead, you need to decode it like
let result = try decode.decode([String : [Car]].self, from: data)
Now you can access them like a dictionar
print(result["cars"]?.first?.name) // Optional("Ferrari")

JSON decoder for Swift dealing with changing underlying JSON with Array and Dictionary

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
}

Swift JSON parse with colon in JSON key name

Can you use Swift's JSONDecoder on JSON that has a colon in the key name? Colons are reserved in Swift for things like indicating Type and Protocol, so when I try to create a struct with a matching keyname to use with JSONDecoder.decode, I'll get an error. Example API:
https://api.teleport.org/api/urban_areas/
the list of cities I'd like to access is in the key:
"ua:item"
But Swift doesn't allow a property with this name for easy JSONDecoding.
I'd like to stick with JSONDecoder since it's so easy & elegant. Is there an easy workaround for this, or do I need to fall back on older parsing techniques. Thanks!
Here is a reduced version of the JSON response for the API that you are calling. I have just limited the number of items in each array as it becomes to repetitive to just list similar items over and over again.
let data = """
{
"_links": {
"curies": [
{
"href": "https://developers.teleport.org/api/resources/Location/#!/relations/{rel}/",
"name": "location",
"templated": true
}
],
"self": {
"href": "https://api.teleport.org/api/urban_areas/"
},
"ua:item": [
{
"href": "https://api.teleport.org/api/urban_areas/slug:aarhus/",
"name": "Aarhus"
},
{
"href": "https://api.teleport.org/api/urban_areas/slug:adelaide/",
"name": "Adelaide"
}
]
},
"count": 266
}
""".data(using: .utf8)!
There are are couple of gotchas in the JSON, but we can fix them easily by using CodingKeys. Notice the case value of the CodingKey must match the variable name, the string value of the coding key must match the value in the JSON, where the values are the same we can skip writing the string value.
There are three issues with the JSON
_links
self
ua:item
By convention variables in Swift do not usually start with an underscore, so it makes sense to remove that. We can do that with the first set of CodingKeys.
self is a reserved word in Swift so we should replace that with something more appropriate, in this case I chose link.
As you have already noticed you cannot have colons in the name of variables. We can replace this with something more suitable, in this case uaItem.
This gives the following struct. Which if you take with the above data variable and past into a playground it should all decode nicely.
struct Response: Decodable {
let links: Links
let count: Int
// These are the coding keys for Response
enum CodingKeys: String, CodingKey {
case links = "_links"
case count
}
struct Links: Decodable {
let curies: [Curie]
let link: HREF
let uaItem: [UAItem]
// These are the coding keys for Links
enum CodingKeys: String, CodingKey {
case curies
case link = "self"
case uaItem = "ua:item"
}
}
struct Curie: Decodable {
let href: String
let name: String
let templated: Bool
}
struct HREF: Decodable {
let href: String
}
struct UAItem: Decodable {
let href: String
let name: String
}
}
do {
let result = try JSONDecoder().decode(Response.self, from: data)
print(result)
} catch {
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.

Parsing JSON using codable and ignoring first layer of JSON

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.