JSONEncoder() 按不同类型对同一键进行编码 (Swift)

JSONEncoder() encoding of the same key by different types (Swift)

提问人:diclonius9 提问时间:10/24/2023 最后编辑:Dávid Pásztordiclonius9 更新时间:10/25/2023 访问量:49

问:

我有一个无法克服的问题,即如何使用 JSONEncoder() 对服务器请求的有效负载进行编码。该对象应如下所示:

{
  "filter": {
     "conditions": [
        {"key": "id_wbs", "values": [1293548]},
        {"key": "id_object", "values": []},
        {"key": "id", "values": [""]},
        {"key": "period", "values": ["month"]},
        {"key": "type_chart", "values": [""]},
        {"key": "tzr_type", "values": ["monthly"]},
        {"key": "type_of_work", "values": []},
        {"key": "id_group_object", "values": []},
        {"key": "report_date", "values": [],
            "value_derived": {
            "columns": "max_report_date",
            "bo_id": 100006,
            "filter": {
                "conditions": [{"key": "id_wbs", "values": [1293548]}, {"key": "operation", "values": [""]}]
                }
            }
        }]
    },
} 

问题出在“值”键上。根据键的值,它们可以是整数数组、字符串数组或日期数组。坦率地说,我对此感到困惑,现在陷入了僵局。我将不胜感激任何帮助

初步数据模型如下所示:

struct CSITableRequest: Encodable {
   let filter: TableFilter
}

struct TableFilter: Encodable {
   let conditions: [TableFilterConditionItem]
}

struct TableFilterConditionItem: Encodable {
   let key: TableFilterConditionKey
   let values: [String] //[Int]; [Date] - This is a problematic key, which can be either an Int array, a String array, or a Date array
   let value_derived: TableValueDerived?
}

enum TableFilterConditionKey: String, Encodable {
   case id_wbs
   case id_object
   case id
   case period
   case type_chart
   case tzr_type
   case type_of_work
   case id_group_object
   case report_date 
   case operation
}

struct TableValueDerived: Encodable {
   let columns: String
   let bo_id: Int
   let filter: TableFilter
}
JSON Swift 可编码

评论


答:

2赞 Sweeper 10/24/2023 #1

您可以创建一个具有关联值的枚举来表示“字符串数组或 int 数组或日期数组”的概念。通过将调用委托给 // 方法来符合此类型。Encodableencode[Int][String][Date]

enum FilterValues: Encodable {
    case strings([String])
    case ints([Int])
    case dates([Date])
    
    func encode(to encoder: Encoder) throws {
        switch self {
        case .strings(let strings):
            try strings.encode(to: encoder)
        case .ints(let ints):
            try ints.encode(to: encoder)
        case .dates(let dates):
            try dates.encode(to: encoder)
        }
    }
}
let values: FilterValues
2赞 Rob Napier 10/25/2023 #2

@Sweeper 的答案非常灵活,可能是理想的,但假设每个键的值都有特定的类型,我更愿意让整个事情的类型更加安全。这可能会使编写起来稍微乏味一些,但代码并不困难。

不是键和值,而是将它们放在一个 KeyValue 枚举中。这是有点乏味的部分,一些代码一遍又一遍地重复,但它确保类型对齐。

enum TableFilterConditionKeyValue: Encodable {
    case id_wbs([Int])
    case id_object([String])
    case id([String])
    case period([String])
    case type_chart([String])
    case tzr_type([String])
    case type_of_work([String])
    case id_group_object([String])
    case report_date([Date])
    case operation([String])

    enum CodingKeys: String, CodingKey {
        case key
        case values
    }

    private var key: String {
        switch self {
        case .id_wbs(_): "id_wbs"
        case .id_object(_): "id_object"
        case .id(_): "id"
        case .period(_): "period"
        case .type_chart(_): "type_chart"
        case .tzr_type(_): "tzr_type"
        case .type_of_work(_): "type_of_work"
        case .id_group_object(_): "id_group_object"
        case .report_date(_): "report_date"
        case .operation(_): "operation"
        }
    }

    private func encode(_ values: some Encodable, to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(key, forKey: .key)
        try container.encode(values, forKey: .values)
    }

    func encode(to encoder: Encoder) throws {
        switch self {
        case .id_wbs(let values): try encode(values, to: encoder)
        case .id_object(let values): try encode(values, to: encoder)
        case .id(let values): try encode(values, to: encoder)
        case .period(let values): try encode(values, to: encoder)
        case .type_chart(let values): try encode(values, to: encoder)
        case .tzr_type(let values): try encode(values, to: encoder)
        case .type_of_work(let values): try encode(values, to: encoder)
        case .id_group_object(let values): try encode(values, to: encoder)
        case .report_date(let values): try encode(values, to: encoder)
        case .operation(let values): try encode(values, to: encoder)
        }
    }
}

这样,您可以按以下方式对 TableFilterConditionItem 进行编码:

struct TableFilterConditionItem: Encodable {
   let keyValue: TableFilterConditionKeyValue
   let value_derived: TableValueDerived?

   init(_ keyValue: TableFilterConditionKeyValue, value_derived: TableValueDerived? = nil) {
        self.keyValue = keyValue
        self.value_derived = value_derived
    }
    enum CodingKeys: CodingKey {
        case value_derived
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try keyValue.encode(to: encoder)
        try container.encodeIfPresent(self.value_derived, forKey: .value_derived)
    }
}

这样,您的数据结构就是:

let value = CSITableRequest(filter: TableFilter(conditions: [
    TableFilterConditionItem(.id_wbs([1293548])),
    TableFilterConditionItem(.id_object([])),
    TableFilterConditionItem(.id([""])),
    TableFilterConditionItem(.period(["month"])),
    TableFilterConditionItem(.type_chart([""])),
    TableFilterConditionItem(.tzr_type(["monthly"])),
    TableFilterConditionItem(.type_of_work([])),
    TableFilterConditionItem(.id_group_object([])),
    TableFilterConditionItem(.report_date([]), value_derived:
                            TableValueDerived(columns: "max_report_date", bo_id: 100006, filter:
                                             TableFilter(conditions: [
                                                TableFilterConditionItem(.id_wbs([1293548])),
                                                TableFilterConditionItem(.operation([])),
                                             ])))
]))