Given what you've described, you should store params as an enum like this:
enum Param: CustomStringConvertible {
case string(String)
case int(Int)
case array([Param])
var description: String {
switch self {
case let .string(string): return string
case let .int(int): return "\(int)"
case let .array(array): return "\(array)"
}
}
}
A param can either be a string, an int, or an array of more params.
Next, you can make Param Decodable by trying each option in turn:
extension Param: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let string = try? container.decode(String.self) {
self = .string(string)
} else if let int = try? container.decode(Int.self) {
self = .int(int)
} else {
self = .array(try container.decode([Param].self))
}
}
}
Given this, there's no need for custom decoding logic in LMSRequest:
struct LMSRequest: Decodable {
let id : Int?
let method : String?
let params : [Param]?
}
As a side note, I would carefully consider whether these fields are all truly optional. It's very surprising that id is optional, and quite surprising that method is optional, and slightly surprising that params are optional. If they're not really optional, don't make them optional in the type.
From your comments, you're probably misunderstanding how to access enums. params[1] is not a [Param]. It's an .array([Param]). So you have to pattern match it since it might have been a string or an int.
if case let .array(values) = lms.params[1] { print(values[0]) }
That said, if you're doing this a lot, you can make this simpler with extensions on Param:
extension Param {
var stringValue: String? { if case let .string(value) = self { return value } else { return nil } }
var intValue: Int? { if case let .int(value) = self { return value } else { return nil } }
var arrayValue: [Param]? { if case let .array(value) = self { return value } else { return nil } }
subscript(_ index: Int) -> Param? {
return arrayValue?[index]
}
}
With that, you can say things like:
let serverstatus: String? = lms.params[1][0]?.stringValue
Which is probably closer to what you had in mind. (The : String? is just to be clear about the returned type; it's not required.)
For a more complex and worked-out example of this approach, see my generic JSON Decodable that this is a subset of.
paramsis a[String]?, but as you note it includes both a string and a nested array of strings. What do you want he finalparamsto look like? Should it flatten them or something else? There is also a1in the middle of the nested data array, which is not a String. Do you want this converted to a String"1"or something else? - Rob NapierCatobjects in there?Houseobjects? Do you really mean "absolutely any object at all, even ones that can't be expressed in JSON?" Or do you mean "a small list of types, probably just strings and ints" in which case you mean an enum. Or, looking at this, I suspect it is really meant to be a all strings and want conversions. But you'll need to work out your Swift type first, and then we can decode into it. - Rob Napier