Swift 4.1 кодируемый / декодируемый вложенный массив

Нужна помощь с более сложным json с новейшим кодировщиком / декодером swift4.1:

структура:

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" ... действительно застрял в том, как это построить, спасибо!


person Scott Kramer    schedule 10.06.2018    source источник
comment
params - это [String]?, но, как вы заметили, он включает как строку, так и вложенный массив строк. Как вы хотите, чтобы он выглядел в итоге params? Должен ли он их сплющить или что-то еще? Также есть 1 в середине вложенного массива данных, который не является строкой. Вы хотите, чтобы это преобразовалось в String "1" или что-то еще?   -  person Rob Napier    schedule 10.06.2018
comment
Глядя на это - массив объектов - поскольку там есть int. Однако нужно, чтобы вложенный массив стал первым.   -  person Scott Kramer    schedule 10.06.2018
comment
Массив объектов на самом деле мало что значит. Вы имеете в виду, что там может быть Cat объектов? House объектов? Вы действительно имеете в виду абсолютно любой объект, даже тот, который не может быть выражен в JSON? Или вы имеете в виду небольшой список типов, вероятно, только строки и целые числа, и в этом случае вы имеете в виду перечисление. Или, глядя на это, я подозреваю, что это действительно все строки и требуется преобразование. Но сначала вам нужно определить свой тип Swift, а затем мы сможем его декодировать.   -  person Rob Napier    schedule 10.06.2018
comment
струны и вставки   -  person Scott Kramer    schedule 10.06.2018


Ответы (1)


Учитывая то, что вы описали, вы должны хранить параметры в виде перечисления, например:

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 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? просто указывает на возвращаемый тип; это не обязательно.)

Более сложный и проработанный пример этого подхода см. В моем общем JSON Decodable, является подмножеством.

person Rob Napier    schedule 10.06.2018
comment
Спасибо! Это сработало сразу, помогает понять, как это работает. - person Scott Kramer; 11.06.2018
comment
let lms = try decoder.decode (LMSRequest.self, from: json) print (lms.params [1]) - ›[serverstatus, -, 1, tags: GPASIediqtymkovrfijnCYXRTIuwxNlasc] - не удается получить доступ к этому внутреннему массиву ... - person Scott Kramer; 11.06.2018
comment
gist - ›gist.github.com/sckramer/5b90d3b15eef2c19196abab70e - person Scott Kramer; 11.06.2018