Anand Yadav
Anand Yadav

Reputation: 489

Using Decodable to Decode different array of objects in same key

I'm working with an API that provides me with Json as follows

{
  "data": [
    {
      "title": "Banners",
      "type": "banners",
      "sequence": 1,
      "is_enabled": 1,
      "categories": [
        {
          "title": "banner-01",
          "image": "http://design.example.com/b2c/assets/banners/banner-01.png?v=1",
          "method": "onPressBanner",
          "url": "https://example.ng/download",
          "description": null
        }
      ]
    },
    {
      "title": "Current Orders",
      "type": "orders",
      "sequence": 4,
      "is_enabled": 1,
      "categories": [
        {
          "customer_id": 26042,
          "customer": "XYZ",
          "order_id": 2071,
          "order_status_id": 9,
          "status": "complete",
          "order_type": "product",
          "product_id": 2075,
          "product_name": "Cholesterol Regulator",
          "total_amount": 10750,
          "is_payment_received": 1,
          "paymentMethod": "pos",
          "payable_amount": 10750,
          "added_on": "2018-11-27T01:45:09.000Z"
        }
      ]
    }
  ]
}

Now the problem is that I am getting different type of array of object in categories key, so I am trying to solve this problem by making categories generic of type DataList

My Decodable classes are as follows

struct DataList<T : Decodable> : Decodable {
    let dataList: [T]
}

This is my struct for Dashboard class for outer object

struct DashBoard<T : Decodable> : Decodable {

    let title: String?
    let type: String?
    let sequence: Int?
    let is_enabled: Int?
    let categories : DataList<T>?
    var cellType: DashBoardSectionType?

    enum CodingKeys: String, CodingKey {
        case title = "title"
        case type = "type"
        case categories = "categories"
        case sequence = "sequence"
        case is_enabled = "is_enabled"
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        title = try values.decodeIfPresent(String.self, forKey: .title)
        type = try values.decodeIfPresent(String.self, forKey: .type)
        is_enabled = try values.decodeIfPresent(Int.self, forKey: .is_enabled)
        sequence = try values.decodeIfPresent(Int.self, forKey: .sequence)

        cellType = DashBoardSectionType(rawValue: type!) ?? .OTHER
        if cellType == .ORDERS {
            categories = try (values.decodeIfPresent(DataList<Order>.self, forKey: .categories) as? DataList<T>)
        }else{
            categories = try (values.decodeIfPresent(DataList<Category>.self, forKey: .categories) as? DataList<T>)
        }
    }
}

Then are separate structure for separate objects inside categories one is orders and other is category

Order

   struct Order: Codable {
        let customerID: Int
        let customer: String
        let orderID, orderStatusID: Int
        let status, orderType: String
        let productID: Int
        let productName: String
        let totalAmount, isPaymentReceived: Int
        let paymentMethod: String
        let payableAmount: Int
        let addedOn: String

        enum CodingKeys: String, CodingKey {
            case customerID = "customer_id"
            case customer
            case orderID = "order_id"
            case orderStatusID = "order_status_id"
            case status
            case orderType = "order_type"
            case productID = "product_id"
            case productName = "product_name"
            case totalAmount = "total_amount"
            case isPaymentReceived = "is_payment_received"
            case paymentMethod
            case payableAmount = "payable_amount"
            case addedOn = "added_on" 
        }

        init(from decoder: Decoder) throws {
            let values = try decoder.container(keyedBy: CodingKeys.self)
            orderID = try values.decodeIfPresent(Int.self, forKey: .orderID) ?? 0
            customer = try values.decodeIfPresent(String.self, forKey: .customer) ?? ""
            customerID = try values.decodeIfPresent(Int.self, forKey: .customerID) ?? 0
            status = try values.decodeIfPresent(String.self, forKey: .status) ?? ""
            orderStatusID = try values.decodeIfPresent(Int.self, forKey: .orderStatusID) ?? 0
            payableAmount = try values.decodeIfPresent(Int.self, forKey: .payableAmount) ?? 0
            addedOn = try values.decodeIfPresent(String.self, forKey: .addedOn) ?? ""
            orderType = try values.decodeIfPresent(String.self, forKey: .orderType) ?? ""
            productID = try values.decodeIfPresent(Int.self, forKey: .productID) ?? 0
            productName = try values.decodeIfPresent(String.self, forKey: .productName) ?? ""
            isPaymentReceived = try values.decodeIfPresent(Int.self, forKey: .isPaymentReceived) ?? 0
            totalAmount = try values.decodeIfPresent(Int.self, forKey: .productName) ?? 0
            paymentMethod = try values.decodeIfPresent(String.self, forKey: .paymentMethod) ?? ""
        }
    }

Category

struct Category: Decodable {
    let title : String?
    let image : String?
    let method : String?
    let url : String?
    let description: String?

    enum CodingKeys: String, CodingKey {

        case title = "title"
        case image = "image"
        case method = "method"
        case url = "url"
        case description
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        title = try values.decodeIfPresent(String.self, forKey: .title)
        image = try values.decodeIfPresent(String.self, forKey: .image)
        method = try values.decodeIfPresent(String.self, forKey: .method)
        url = try values.decodeIfPresent(String.self, forKey: .url)
        description = try values.decodeIfPresent(String.self, forKey: .description)
    }
}   

When I try to decode Dashboard, I get following error enter image description here

let decoder = JSONDecoder()
let dashBoardArr = try decoder.decode([DashBoard].self, from: data)

basically Xcode is asking me to specify the type for generic type of Decodable

Please suggest how can I get over this

Upvotes: 2

Views: 1439

Answers (1)

Kamran
Kamran

Reputation: 15258

You can create another type to keep Orders or Categories and use a type to know which one was parsed from the response. See the below implementation to handle the two type for the same key i.e, categories.

struct DataList: Decodable {
    let data: [DashBoard]
}

enum OrderOrCategoryType {
    case unknown, order, category
}

struct OrderOrCategory {
    var order: [Order]?
    var categories: [Category]?
}

struct DashBoard: Decodable {

    let title: String?
    let type: String?
    let sequence: Int?
    let is_enabled: Int?
    let categories: OrderOrCategory?
    var categoryType: OrderOrCategoryType = .unknown

    enum CodingKeys: String, CodingKey {
        case title = "title"
        case type = "type"
        case categories = "categories"
        case sequence = "sequence"
        case is_enabled = "is_enabled"
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        title = try values.decodeIfPresent(String.self, forKey: .title)
        type = try values.decodeIfPresent(String.self, forKey: .type)
        is_enabled = try values.decodeIfPresent(Int.self, forKey: .is_enabled)
        sequence = try values.decodeIfPresent(Int.self, forKey: .sequence)
        if let orders = try? values.decodeIfPresent([Order].self, forKey: .categories) {
            self.categories = OrderOrCategory(order: orders, categories: nil)
            categoryType = .order
        } else {
            categoryType = .category
            let categories = try values.decodeIfPresent([Category].self, forKey: .categories)
            self.categories = OrderOrCategory(order: nil, categories: categories)
        }
    }
}

Now you can parse it as this,

let dataList = try decoder.decode(DataList.self, from: data)

Upvotes: 3

Related Questions