skip to Main Content

I have an insurmountable problem of how to encode a payload for a request to the server using JSONEncoder(). The object should look like this:

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

The problem is in the “values" keys. Depending on the value of the key, they can be either an array of integers, or an array of strings, or an array of dates. Frankly, I got confused with this and am now at an impasse. I will be grateful for any help

The preliminary data model looks like this:

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
}

2

Answers


  1. You can create an enum with associated values to represent the idea of "string array or int array or date array". Conform this type to Encodable by delegating the call to the encode method of [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
    
    Login or Signup to reply.
  2. @Sweeper’s answer is extremely flexible and may be ideal, but assuming that each key has a specific type for its values, I prefer to make the whole thing more type-safe. This can get slightly more tedious to write, but the code is not difficult.

    Rather than a key and value, you would put them together into a KeyValue enum. This is the somewhat tedious part, with some code repeated over and over again, but it makes sure the types line up.

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

    With that, you can encode TableFilterConditionItem this way:

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

    With that, your data structure is:

    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([])),
                                                 ])))
    ]))
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search