Swift 4.1 Codable/Decodable Nested Array

混江龙づ霸主 提交于 2019-12-08 23:35:34

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.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!