使用Decodable解码同一键中的不同对象数组

时间:2018-11-30 11:33:54

标签: ios swift codable decodable

我正在使用向我提供Json的API,如下所示

{
  "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"
        }
      ]
    }
  ]
}

现在的问题是,我在 categories 键中获得了不同类型的对象数组,因此我试图通过使 categories 泛型来解决此问题。 类型为 DataList

我的可降解类如下

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

这是我的外部对象 Dashboard 类的结构

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>)
        }
    }
}

然后是类别内单独对象的单独结构,一个是订单,另一个是类别

订购

   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) ?? ""
        }
    }

类别

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)
    }
}   

当我尝试解码仪表板时,出现以下错误 enter image description here

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

基本上Xcode要求我为Decodable的通用类型指定类型

请提出如何解决这个问题

1 个答案:

答案 0 :(得分:1)

您可以创建另一种类型来保留OrdersCategories,并使用type来从响应中解析出哪一个类型。请参见下面的实现,以针对相同的键(即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)
        }
    }
}

现在您可以将其解析为这样,

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