Reputation: 489
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
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
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