-1

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"))
    }
}
Luciano Perez
  • 101
  • 1
  • 10
  • Is this representing all the values that can go into the json ? or you could have other types besides journey, bookings – Petar Feb 28 '23 at 11:58
  • Could you please show us what you tried before? – Omer Tekbiyik Feb 28 '23 at 12:00
  • @Petar this are all the types you can get, in the example you get in the position zero a Journey and array of Bookigs and the position 2 and 3 you just get just a plain booking. – Luciano Perez Feb 28 '23 at 12:34
  • @OmerTekbiyik I just added the stuff I tried so far but it does not work. – Luciano Perez Feb 28 '23 at 12:35
  • 1
    Your model does not match the given json. Go to https://app.quicktype.io and generate an appropriate model. No need for coding key. – burnsi Feb 28 '23 at 12:36
  • @burnsi I know it does not match I just want to know if there is a nice way to deal with this kind of json with different types in an array. – Luciano Perez Feb 28 '23 at 12:54

1 Answers1

1

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
    }
}
Joakim Danielson
  • 43,251
  • 5
  • 22
  • 52