Поддержка Swift Codable для объектов, представленных в виде массива - PullRequest
0 голосов
/ 04 июня 2019

Я пытаюсь кодировать и декодировать данные из API, который представляет объект в виде массива строк, например:

[
  ["username", "message", "date"],
  ["username", "message", "date"],
  ["username", "message", "date"]
]

Это соответствующая Codable структура:

struct Message: Codable {
    let user: String
    let content: String
    let date: String

    private enum CodingKeys: Int, CodingKey {
        case user = 0
        case content = 1
        case date = 2
    }
}

Ни кодирование, ни декодирование не работают; кодировка показывает, что вместо массива создается объект JSON:

let msg = Message(user: "foo", content: "content", date: "2019-06-04")

let jsonData   = try! JSONEncoder().encode(msg)
let jsonString = String(data: jsonData, encoding: .utf8)!

Последняя строка:

{"content":"content","user":"foo","date":"2019-06-04"}

Моя цель - получить следующую строку

["foo", "content", "2019-06-04"]

Использование пользовательского метода кодирования / декодирования в структуре решает эту проблему, но заставляет создавать множество шаблонов для каждой структуры / класса.

init(from decoder: Decoder) throws {
    let container = try decoder.singleValueContainer()
    let values = try container.decode([String].self)
    user    = values[CodingKeys.user.rawValue]
    content = values[CodingKeys.content.rawValue]
    date    = values[CodingKeys.date.rawValue]
}

Как можно было бы поддержать это для любого объекта?

И да, это странный API, но это не первый раз, когда я сталкиваюсь с одним из них, и пользователь не ищет другой формат API здесь.

1 Ответ

0 голосов
/ 05 июня 2019

Вместо singleValueContainer используйте unkeyedContainer, это более надежно. Если вы хотите присвоить элементы массива элементам структуры, вы все равно должны написать собственный инициализатор

struct Message: Codable {
    let user: String
    let content: String
    let date: String

    init(from decoder: Decoder) throws {
        var arrayContainer = try decoder.unkeyedContainer()
        guard arrayContainer.count == 3 else { throw DecodingError.dataCorruptedError(in: arrayContainer, debugDescription: "The array must contain three items") }
        user = try arrayContainer.decode(String.self)
        content = try arrayContainer.decode(String.self)
        date = try arrayContainer.decode(String.self)           
    }

    func encode(to encoder: Encoder) throws {
        var arrayContainer = encoder.unkeyedContainer()
        try arrayContainer.encode(contentsOf: [user, content, date])
    }
}
...