I have an API which returns a payload like this (just one item is included in the example).
{
"length": 1,
"maxPageLimit": 2500,
"totalRecords": 1,
"data": [
{
"date": "2021-05-28",
"peopleCount": 412
}
]
}
I know I can actually create a struct like
struct Root: Decodable {
let data: [DailyCount]
}
struct DailyCount: Decodable {
let date: String
let peopleCount: Int
}
For different calls, the same API returns the same format for the root, but the data is then different. Moreover, I do not need the root info (length
, totalRecords
, maxPageLimit
).
So, I am considering to create a custom init in struct DailyCount
so that I can use it in my URL session
let reports = try! JSONDecoder().decode([DailyCount].self, from: data!)
Using Swift 5 I tried this:
struct DailyCount: Decodable {
let date: String
let peopleCount: Int
}
extension DailyCount {
enum CodingKeys: String, CodingKey {
case data
enum DailyCountCodingKeys: String, CodingKey {
case date
case peopleCount
}
}
init(from decoder: Decoder) throws {
// This should let me access the `data` container
let container = try decoder.container(keyedBy: CodingKeys.self
peopleCount = try container.decode(Int.self, forKey: . peopleCount)
date = try container.decode(String.self, forKey: .date)
}
}
Unfortunately, it does not work. I get two problems:
- The struct seems not to conform anymore to the
Decodable
protocol - The
CodingKeys
does not contain thepeopleCount
(therefore returns an error)
CodePudding user response:
This can’t work for multiple reasons. You are trying to decode an array, so your custom decoding implementation from DailyCount won’t be called at all (if it were to compile) since at the top level your JSON contains an object, not an array.
But there is a much simpler solution which doesn’t even require implementing Decodable yourself.
You can create a generic wrapper struct for your outer object and use that with whatever payload type you need:
struct Wrapper<Payload: Decodable>: Decodable {
var data: Payload
}
You then can use this to decode your array of DailyCount structs:
let reports = try JSONDecoder().decode(Wrapper<[DailyCount]>.self, from: data).data
This can be made even more transparent by creating an extension on JSONDecoder:
extension JSONDecoder {
func decode<T: Decodable>(payload: T.Type, from data: Data) throws -> T {
try decode(Wrapper<T>.self, from: data).data
}
}
CodePudding user response:
Sven's answer is pure and elegant, but I would be remiss if I didn't point out that there is also a stupid but easy way: stop overthinking this and just dumpster-dive into the "data"
without using Codable at all. Example:
// preconditions
let json = """
{
"length": 1,
"maxPageLimit": 2500,
"totalRecords": 1,
"data": [
{
"date": "2021-05-28",
"peopleCount": 412
}
]
}
"""
let jsonData = json.data(using: .utf8)!
struct DailyCount: Decodable {
let date: String
let peopleCount: Int
}
// okay, here we go
do {
let dict = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [AnyHashable:Any]
let arr = dict?["data"] as? Array<Any>
let json2 = try JSONSerialization.data(withJSONObject: arr as Any, options: [])
let output = try JSONDecoder().decode([DailyCount].self, from: json2)
print(output) // yep, it's an Array of DailyCount
} catch {
print(error)
}