Декодировать тип Swift, который является обернутым кодируемым типом с дополнительным кодируемым свойством

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