Swift 4.1 Codable / Decodable嵌套数组

时间:2018-06-10 02:09:31

标签: json swift4.1

使用最新的swift4.1编码器/解码器需要一些更复杂的json的帮助:

结构:

struct LMSRequest: Decodable {
let id : Int?
let method : String?
let params : [String]?
enum CodingKeys: String, CodingKey {
    case id = "id"
    case method = "method"
    case params = "params"
}
init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)
    id = try values.decodeIfPresent(Int.self, forKey: .id)
    method = try values.decodeIfPresent(String.self, forKey: .method)
    params = try values.decodeIfPresent([String].self, forKey: .params)
}}

JSON:

let json = """
{
  "id": 1,
  "method": "slim.request",
  "params": [
    "b8:27:eb:db:6d:62",
    [
      "serverstatus",
      "-",
      1,
      "tags:GPASIediqtymkovrfijnCYXRTIuwxNlasc"
    ]
  ]
}
""".data(using: .utf8)!

代码:

let decoder = JSONDecoder()
let lms = try decoder.decode(LMSRequest.self, from: json)
print(lms)

错误预计会解码字符串但会找到数组。它来自" params"中的嵌套数组。数组...真的坚持如何建立这个,谢谢!

1 个答案:

答案 0 :(得分:2)

鉴于你所描述的内容,你应该将params存储为这样的枚举:

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

param可以是字符串,int或更多参数的数组。

接下来,您可以依次尝试每个选项来制作Param Decodable:

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

鉴于此,LMSRequest中不需要自定义解码逻辑:

struct LMSRequest: Decodable {
    let id : Int?
    let method : String?
    let params : [Param]?
}

作为旁注,我会仔细考虑这些字段是否都是真正可选的。非常令人惊讶的是,id是可选的,并且method是可选的非常令人惊讶,并且params是可选的略微令人惊讶。如果它们不是真正的可选项,请不要在类型中将它们设为可选项。

根据您的评论,您可能误解了如何访问枚举。 params[1]不是[Param]。这是一个.array([Param])。所以你必须模式匹配它,因为它可能是一个字符串或一个int。

if case let .array(values) = lms.params[1] { print(values[0]) }

那就是说,如果你这么做了,你可以通过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]
    }
}

有了这个,你可以这样说:

let serverstatus: String? = lms.params[1][0]?.stringValue

这可能更接近您的想法。 (: String?只是要明确返回的类型;它不是必需的。)

有关此方法的更复杂且经过实践的示例,请参阅我的generic JSON Decodable,这是其中的一个子集。