#swift #codable
#swift #кодируемый
Вопрос:
У меня есть кодируемый тип, скажем Car
, который определяется как:
struct Car: Codable {
let age: Int
let color: String
}
Я могу кодировать / декодировать это просто отлично.
В моей системе сохранения, когда объект сохраняется, ему присваивается _id
свойство, которое является String
, например 5cae04b533376609456d40ed
.
Таким образом, когда я считываю Data
из постоянного хранилища, а затем пытаюсь его декодировать, там есть дополнительные байты, которые представляют _id
свойство и связанное String
с ним значение.
Я не контролирую различные типы, которые могут быть закодированы и сохранены в хранилище. Единственным ограничением для них является то, что они есть Codable
.
Что я хочу иметь возможность сделать, так это декодировать Data
то, что я получаю при чтении из хранилища (с _id
включенным материалом), в тип, который является чем-то вроде Wrapped<T: Codable>
, который был бы определен как что-то вроде (в простейшей форме):
struct Wrapped<T: Codable> {
let _id: String
let value: T
}
Однако я не уверен, что это нужно делать.
Одна из попыток, которую я предпринял, заключалась в определении пользовательской decode
функции, но это не очень далеко, поскольку я, похоже, не могу получить доступ к T
типу CodingKeys
, что делает вещи, насколько я могу судить, невозможными при таком подходе.
Может быть, есть другой подход, который заставил бы все работать так, как мне хотелось бы?
Комментарии:
1. Так должно
_id
быть частью закодированного значения?2. Нет, он должен быть доступен только после того, как произойдет некоторое декодирование, как часть какого-либо другого типа (по крайней мере, я предполагаю, что это будет требованием)
3. Вам не нужно обращаться
T
к ключам кодирования, просто сделайтеvalue = T(from: decoder)
это в своей пользовательскойdecode
функции.4. Однако это не удается, потому что
_id
пара ключ-значение занимает дополнительные байты. Если я не неправильно понял, что вы предлагаете?5. Я написал ответ, показывающий, что я имел в виду
Ответ №1:
Вы можете написать пользовательскую функцию декодирования для своего Wrapped
типа, которая анализирует, _id
а затем передает декодер обернутому типу, чтобы он мог декодировать свои собственные свойства:
struct Wrapped<T: Codable>: Decodable {
let _id: String
let value: T
private enum CodingKeys: String, CodingKey {
case _id
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
_id = try container.decode(String.self, forKey: ._id)
value = try T(from: decoder)
}
}
Комментарии:
1. Это работает, после некоторой настройки с моим пользовательским
Decoder
: github.com/myfreeweb/SwiftCBOR/pull/39 Мне нужно было снятьprecondition
флажок в томDecoder
, что контейнер еще не был создан. Стоит отметить, что для этого нужно декодировать все это дважды, но для меня это не проблема, по крайней мере, не сейчас.
Ответ №2:
Вы можете просто объявить, что _id
свойство не должно декодироваться, определив свой пользовательский CodingKeys
параметр и исключив _id
его оттуда. Вам также необходимо присвоить значение по умолчанию для недекодированных свойств ( _id
в вашем случае), если вы хотите использовать автоматически синтезируемый инициализатор.
Для конкретного типа:
struct Car: Codable {
let age: Int
let color: String
let _id:Int = 0
enum CodingKeys: String, CodingKey {
case age, color
}
}
Вы можете добиться этого для всех ваших сохраняемых типов.
Если вы не хотите создавать CodingKeys
перечисление для всех сохраняемых типов, вы можете следовать общему подходу типа оболочки, который вы начали, но вам нужно будет создать пользовательские init(from:)
encode(to:)
методы и .
struct Persisted<T: Codable>: Codable {
let _id:Int = 0
let value:T
init(from decoder:Decoder) throws {
value = try decoder.singleValueContainer().decode(T.self)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(value)
}
}