Reputation: 119
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
}
Upvotes: 1
Views: 75
Reputation: 299345
@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([])),
])))
]))
Upvotes: 2
Reputation: 271400
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
Upvotes: 2