Hello does anyone know a nice way to decode the below Json? It is array with different elements in it. I tried to decode it using keyedBy for the container but I could not make it work.
For the json example, the first element is a Journey object and an array of Bookings, the object 2 and 3 are just plain bookings.
{
"archives": [
{
"journey": {
"id": 5,
"name": "test name"
},
"bookings": [
{
"id": 563219,
"address": "test address"
},
{
"id": 563220,
"address": "test address 2"
}
]
},
{
"id": 563221,
"address": "test address 3"
},
{
"id": 563222,
"address": "test address 4"
}
]
}
This is what I tried so far but it does not work. It goes 3 times into the Job init because the array has 3 object but It does not know how to decode because the coding keys does not match.
let jobs = try? JSONDecoder().decode(Response.self, from: jsonData)
struct Response: Decodable {
let response: Archive
}
struct Archive: Decodable {
let archives: [Job]
}
struct Booking: Decodable {
let id: Int
let address: String
}
struct JourneyDetail: Decodable {
let journey: Journey?
let bookings: [Booking]?
}
struct Journey: Decodable {
let id: Int
let name: String
}
enum Job: Decodable {
case journeyDetail(JourneyDetail)
case booking(Booking)
enum CodingKeys: CodingKey, CaseIterable {
case journeyDetail
case booking
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let value = try container.decodeIfPresent(JourneyDetail.self, forKey: .journeyDetail) {
self = Job.journeyDetail(value)
return
}
if let value = try container.decodeIfPresent(Booking.self, forKey: .booking) {
self = Job.booking(value)
return
}
throw DecodingError.valueNotFound(Self.self, DecodingError.Context(codingPath: CodingKeys.allCases, debugDescription: "objects not found"))
}
}
Your top level type seems to be irrelevant and I also changed the naming somewhat so here are the structures I used
struct Response: Decodable {
let archives: [Archive]
}
enum Archive: Decodable {
case journeyDetail(JourneyDetail)
case booking(Booking)
}
struct JourneyDetail: Decodable {
let journey: Journey?
let bookings: [Booking]
}
struct Journey: Decodable {
let id: Int
let name: String
}
struct Booking: Decodable {
let id: Int
let address: String
}
What is needed then is to manually decode the top level array archives
so we need coding keys and a custom init in Response
enum CodingKeys: String, CodingKey {
case archives
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
var nestedContainer = try container.nestedUnkeyedContainer(forKey: .archives)
var temp = [Archive]()
while nestedContainer.isAtEnd == false {
if let journey = try? nestedContainer.decode(JourneyDetail.self) {
temp.append(.journeyDetail(journey))
} else {
let booking = try nestedContainer.decode(Booking.self)
temp.append(.booking(booking))
}
}
archives = temp
}
This will give you an array of the enum but another option that might be suitable is to get an array of the struct JourneyDetail
instead.
Here I removed the enum and changed Response
as can be seen below but all other types are the same
struct Response: Decodable {
let archives: [JourneyDetail]
enum CodingKeys: String, CodingKey {
case archives
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
var nestedContainer = try container.nestedUnkeyedContainer(forKey: .archives)
var temp = [JourneyDetail]()
while nestedContainer.isAtEnd == false {
if let archive = try? nestedContainer.decode(JourneyDetail.self) {
temp.append(archive)
} else {
let booking = try nestedContainer.decode(Booking.self)
temp.append(JourneyDetail(journey: nil, bookings: [booking]))
}
}
archives = temp
}
}