I have a JSON response like this:
I have currently designed my decodable struct to be as follows:
struct PortfolioResponseModel: Decodable {
var dataset: Dataset
struct Dataset: Decodable {
var data: Array<PortfolioData> //I cannot use [Any] here...
struct PortfolioData: Decodable {
//how to extract this data ?
}
}
}
The question is, how do I extract the data inside the array, which can have a value Double or String.
Here is the sample string to make this work on playground:
let myJSONArray =
"""
{
"dataset": {
"data": [
[
"2018-01-19",
181.29
],
[
"2018-01-18",
179.8
],
[
"2018-01-17",
177.6
],
[
"2018-01-16",
178.39
]
]
}
}
"""
Extracting the data:
do {
let details2: PortfolioResponseModel = try JSONDecoder().decode(PortfolioResponseModel.self, from: myJSONArray.data(using: .utf8)!)
//print(details2)
//print(details2.dataset.data[0]) //somehow get "2018-01-19"
} catch {
print(error)
}
I cannot use [Any] here.
Never use Any
when decoding JSON because usually you do know the type of the contents.
To decode an array you have to use an unkeyedContainer
and decode the values in series
struct PortfolioResponseModel: Decodable {
var dataset: Dataset
struct Dataset: Decodable {
var data: [PortfolioData]
struct PortfolioData: Decodable {
let date : String
let value : Double
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
date = try container.decode(String.self)
value = try container.decode(Double.self)
}
}
}
}
You can even decode the date strings as Date
struct PortfolioData: Decodable {
let date : Date
let value : Double
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
date = try container.decode(Date.self)
value = try container.decode(Double.self)
}
}
if you add a date formatter to the decoder
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(formatter)
let details2 = try decoder.decode(PortfolioResponseModel.self, from: Data(myJSONArray.utf8))
To add to this, there is a very good example of complex JSON parsing with arrays in particular here. I hope this helps those who are trying to use Codeable with larger, more realistic JSON data.
The overview is this: Imagine you had the following JSON format:
{
"meta": {
"page": 1,
"total_pages": 4,
"per_page": 10,
"total_records": 38
},
"breweries": [
{
"id": 1234,
"name": "Saint Arnold"
},
{
"id": 52892,
"name": "Buffalo Bayou"
}
]
}
This is a common format with the array nested inside. You could create a struct that encapsulates the entire response, accommodating arrays for the "breweries" key, similar to what you were asking above:
struct PagedBreweries : Codable {
struct Meta : Codable {
let page: Int
let totalPages: Int
let perPage: Int
let totalRecords: Int
enum CodingKeys : String, CodingKey {
case page
case totalPages = "total_pages"
case perPage = "per_page"
case totalRecords = "total_records"
}
}
struct Brewery : Codable {
let id: Int
let name: String
}
let meta: Meta
let breweries: [Brewery]
}