How to decode JSON in Swift? - json

I have an API JSON response like below. I want to decode the JSON to get an array of dictionary [String:Double], like [{"2020-01-01" : 0.891186}, {"2020-01-02" : 0.891186}].
{
"rates": {
"2020-01-01": {
"EUR": 0.891186
},
"2020-01-02": {
"EUR": 0.891186
},
"2020-01-03": {
"EUR": 0.895175
},
"2020-01-04": {
"EUR": 0.895175
}
}
}
I have written decode code like below:
do {
let data = try Data(contentsOf: appURL)
let decoder = JSONDecoder()
let response = try decoder.decode(Rates.self, from: data)
response.rates
} catch let jsonError {
print(jsonError)
}
And I have tried to define a struct:
struct Rates: Codable, Hashable {
let rates: Point
}
struct Point {
}
But I don't have an idea about what I should write in struct Point because the date is not a consistent field.

Here are two possible solutions, one with the struct Point and the other one with a dictionary instead
First the solution with Point
struct Point: Codable {
let date: String
let rate: Double
}
And then create a custom init(from:) where we decode the json into a dictionary first, [String: [String: Double]] and then map that dictionary into an array of Point
struct Rates: Codable {
let rates: [Point]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let dictionary = try container.decode([String: [String: Double]].self, forKey: .rates)
rates = dictionary.map { Point(date: $0.key, rate: $0.value.first?.value ?? .zero) }
}
}
And here is the second solution using a dictionary
struct Rates: Codable {
let rates: [String: Double]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let dictionary = try container.decode([String: [String: Double]].self, forKey: .rates)
rates = dictionary.compactMapValues { $0.first?.value }
}
}

struct Rates: Codable {
let rates: [String: Point]
}
// MARK: - Point
struct Point: Codable {
let eur: Double
enum CodingKeys: String, CodingKey {
case eur = "EUR"
}
}
you can do something like this

Related

Decode json with key and Any type Value

I have a JSON file and I want to use [String: Any]. The problem I'm having is that I get an error:
No exact matches in call to instance method 'decode'
I would love to understand where I am wrong.
struct MyModel: Decodable {
let myDictionary: [String: Any]
private enum CodingKeys: String, CodingKey {
case myDictionary
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let data = try container.decode([String: Any].self, forKey: .myDictionary)
self.myDictionary = data
}
}
The problem is that a [String: Any] dictionary is not compliant to the Decodable protocol because of the Any part. Which concrete type is not automatically inferred.
But don't worry, in your case this is not a problem. You have just to read your file content and use JSONSerialization to cast it to [String: Any].
import Foundation
let json = """
{
"object": "customer",
"id": "4yq6txdpfadhbaqnwp3",
"email": "john.doe#example.com",
"metadata": {
"link_id": "linked-id",
"buy_count": 4
}
}
"""
struct Model {
var dictionary: [String: Any]
init() {
dictionary = [:]
}
init(data: Data) {
self.init()
if let dictionary = try? JSONSerialization.jsonObject(with: data, options: []) as? [String:Any] {
self.dictionary = dictionary
}
}
init(string: String) {
self.init()
if let data = string.data(using: .utf8) {
self.init(data: data)
}
}
}
You can then decide if you want to create your Model from a String or Data.

Swift json dynamic key parsing for json

I have json response where only just one key name change rest is same and want to parse without duplicating same struct again.
"attributes": {
"symbol":"EUR",
"name":"Euro",
"precision":2,
}
"attributes":{
"symbol":"EUR",
"name":"Euro",
"precision_for_fiat_price":2,
}
How can handle this precision key dynamically in json parsing
You can use a custom keyDecodingStrategy.
Essentially, you write some logic that checks whether the current coding key path matches some criteria, and if it does, map that key to the precision key.
For example:
struct Root : Codable {
let attributes: Attributes
}
struct Attributes : Codable {
let symbol: String
let name: String
let precision: Int
enum CodingKeys: CodingKey {
case symbol
case name
case precision
}
}
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .custom({
keys in
// This will decode every precision_for_fiat_price key in the json as "precision".
// You might not want this.
// Make this criteria stricter if you need to. An example is shown below
if keys.last?.stringValue == "precision_for_fiat_price" {
return Attributes.CodingKeys.precision
}
// this will only decode those precision_for_fiat_price that have "attribute" as their parent as "precision"
// if stringPath.suffix(2) == ["attributes", "precision_for_fiat_price"] {
// return Attributes.CodingKeys.precision
// }
return keys.last!
})
let json = """
{
"attributes":{
"symbol":"EUR",
"name":"Euro",
"precision_for_fiat_price":2
}
}
""".data(using: .utf8)!
let decoded = try decoder.decode(Root.self, from: json)
If you want to just decode json model like this:
let json = """
{
"attributes": {
"symbol":"EUR",
"name":"Euro",
"precision_for_fiat_price":2 // or "precision": 2
}
}
"""
You can create Decodable struct:
struct WrapperModel: Decodable { // any model
var attributes: Attributes
}
struct Attributes : Decodable {
let symbol: String
let name: String
var precision: Int = 0
enum CodingKeys: String, CodingKey, CaseIterable {
case symbol
case name
case precision
case precisionAnother = "precision_for_fiat_price"
// you can write any types of key here
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
symbol = try container.decode(String.self, forKey: .symbol)
name = try container.decode(String.self, forKey: .name)
if let precisionValue = try container.decodeIfPresent(Int.self, forKey: .precision) {
precision = precisionValue
}
if let precisionValue = try container.decodeIfPresent(Int.self, forKey: .precisionAnother) {
precision = precisionValue
}
}
}
You can test it with:
let jsonData = Data(json.utf8)
let decoder = JSONDecoder()
do {
let attributes = try decoder.decode(WrapperModel.self, from: jsonData)
print(attributes)
} catch {
print(error.localizedDescription)
}

How do I parse JSON with different Structures?

I have a the following structure:
JSON a:
{
"type": "A",
"data": {
"aSpecific": 64
}
}
or JSON b:
{
"type": "B",
"data": {
"bSpecific": "hello"
}
}
Now how does the structure look like to parse any of the above in one go?
enum DataType {
case "A"
case "B"
}
struct Example: Codable {
struct ASpecific: Codable {
var aSpecifiv: Int
}
struct BSpecific: Codable {
var bSpecifiv: String
}
var type: DataType
var data: ??? // Aspecific or BSpecific
}
I want the var data to be specific for the type of the JSON.
How do I do this?
First of all, use String as rawType of enum DataType
enum DataType: String, Decodable {
case A, B
}
Now, you can create init(from:) in the struct Root and add custom parsing as per the JSON.
struct Root: Decodable {
let type: DataType
let aSpecific: Int?
let bSpecific: String?
enum CodingKeys: String, CodingKey {
case type, data, aSpecific, bSpecific
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
type = try container.decode(DataType.self, forKey: .type)
let data = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .data)
aSpecific = try data.decodeIfPresent(Int.self, forKey: .aSpecific)
bSpecific = try data.decodeIfPresent(String.self, forKey: .bSpecific)
}
}
Parsing:
do {
let response = try JSONDecoder().decode(Root.self, from: data)
print(response)
} catch {
print(error)
}
For two different types and one common key my suggestion is an enum with associated values. It's easy to distinguish on the basis of the type and you don't need any optionals.
enum DataType : String, Decodable {
case A, B
}
struct ASpecific: Decodable {
var aSpecific: Int
}
struct BSpecific: Decodable {
var bSpecific: String
}
enum Response : Decodable {
case aType(ASpecific)
case bType(BSpecific)
private enum CodingKeys : String, CodingKey {
case type, data
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(DataType.self, forKey: .type)
switch type {
case .A: self = .aType(try container.decode(ASpecific.self, forKey: .data))
case .B: self = .bType(try container.decode(BSpecific.self, forKey: .data))
}
}
}

How to use decodable protocol with custom type values in Swift?

I have 2 types of response depending on my reuest: First one:
{
"status": "success"
"data": {
"user_id": 2,
"user_name": "John"
}
}
And second one is:
{
"status": "error",
"data": [],
}
I am using struct like that:
struct ValyutaListData:Decodable {
let status: String?
let data: [String]?
}
But if response is first type response, then an error occured. Because In first Type response data is not array. It is Json object. Then i use structure like that:
struct ValyutaListData:Decodable {
let status: String?
let data: Persondata?
}
struct Persondata: Decodable{
let user_id: Int?
let user_name: String?
}
If response is second type response, the error will be occured. What kind of of structure should use for dynamic type JSONs? Thanks.
One reasonable solution is an enum with associated type(s)
struct User : Decodable {
let userId: Int
let userName: String
}
enum Result : Decodable {
case success(User), failure
enum CodingKeys: String, CodingKey { case status, data }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let status = try container.decode(String.self, forKey: .status)
if status == "success" {
let userData = try container.decode(User.self, forKey: .data)
self = .success(userData)
} else {
self = .failure
}
}
}
And use it
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let result = try decoder.decode(Result.self, from: data)
switch result {
case .success(let user): print(user)
case .failure: print("An error occurred")
}
} catch { print(error) }

Swift, How to Parse/Decode the JSON using Decodable and Codable, When key are unknow/dynamic

Below is my JSON, and I am not able to decode(using CodingKeys)
The data within the regions key is a Dictionary ("IN-WB", "IN-DL" & so on....), as the keys are dynamic, it can be changed more or less.
Please help me parsing the same using Decodable and Codable.
All the data should be within the single model.
{
"provider_code": "AIIN",
"name": "Jio India",
"regions": [
{
"IN-WB": "West Bengal"
},
{
"IN-DL": "Delhi NCR"
},
{
"IN-TN": "Tamil Nadu"
},
{
"IN": "India"
}
]
}
Just use a Dictionary for the regions.
struct Locations: Codable {
let providerCode: String
let name: String
let regions: [[String: String]]
enum CodingKeys: String, CodingKey {
case providerCode = "provider_code"
case name, regions
}
}
You cannot create a specific model for the regions as you wont know the property names
One of possible approach, without using dictionary. But still we have to found key at first )
I like this style as we can use Regions from beginning.
// example data.
let string = "{\"provider_code\":\"AIIN\",\"name\":\"Jio India\",\"regions\":[{\"IN-WB\":\"West Bengal\"},{\"IN-DL\":\"Delhi NCR\"},{\"IN-TN\":\"Tamil Nadu\"},{\"IN\":\"India\"}]}"
let data = string.data(using: .utf8)!
// little helper
struct DynamicGlobalKey: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
}
// model
struct Location: Decodable {
let providerCode: String
let name: String
let regions: [Region]
}
extension Location {
struct Region: Decodable {
let key: String
let name: String
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: DynamicGlobalKey.self)
key = container.allKeys.first!.stringValue
name = try container.decode(String.self, forKey: container.allKeys.first!)
}
}
}
// example of decoding.
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let location = try decoder.decode(Location.self, from: data)