2

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

3
  • You don't need generics. Check "type": "banners | "type": "orders" and decode categories manually. Commented Nov 30, 2018 at 11:44
  • @Desdenova can you please elaborate Commented Nov 30, 2018 at 11:52
  • Go to your search engine and type stackoverflow codable undetermined Commented Nov 30, 2018 at 11:58

1 Answer 1

3

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)
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.