Один довольно простой подход состоит в том, чтобы обернуть это в тип общего назначения Response
, который предполагает, что первый ключ всегда является правильным.
struct AnyStringKey: CodingKey {
var stringValue: String
init?(stringValue: String) { self.stringValue = stringValue }
var intValue: Int?
init?(intValue: Int) { return nil }
}
struct Response<Payload: Decodable>: Decodable {
let payload: Payload
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: AnyStringKey.self)
guard let key = container.allKeys.first else {
throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath,
debugDescription: "Missing payload key"))
}
self.payload = try container.decode(Payload.self, forKey: key)
}
}
let user = try JSONDecoder().decode(Response<User>.self, from: json).payload
Возможно сделать это более продвинутым, иубедитесь, что ключ соответствует вашему ожиданию, но этого, вероятно, достаточно для вашей ситуации.
Этот подход переносит некоторую работу в вызывающую сторону (вызов .payload
).Вы можете избавиться от этого за счет перевода части работы в декодированный тип с помощью протокола, который обрабатывает извлечение подключей.
protocol LayerDecodable: Decodable {
associatedtype CodingKeys: CodingKey
init(from container: KeyedDecodingContainer<CodingKeys>) throws
}
extension LayerDecodable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: AnyStringKey.self)
guard let key = container.allKeys.first else {
throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath,
debugDescription: "Missing payload key"))
}
try self.init(from: container.nestedContainer(keyedBy: CodingKeys.self, forKey: key))
}
}
Но при этом вам необходимо реализовать декодирование вручную.
struct User: LayerDecodable {
let email: String
let token: String
enum CodingKeys: CodingKey {
case email, token
}
init(from container: KeyedDecodingContainer<CodingKeys>) throws {
self.email = try container.decode(String.self, forKey: .email)
self.token = try container.decode(String.self, forKey: .token)
}
}
Преимущество заключается в том, что звонящий теперь чист:
let user = try JSONDecoder().decode(User.self, from: json)