Я пытаюсь принять протокол Codable
для объекта, который должен быть создан из JSON, который моя веб-служба возвращает в ответ на один из вызовов API.
Одно из свойств имеет тип перечисления и является необязательным: nil
означает, что ни один из параметров, определенных enum
, не выбран.
Константы enum
основаны на Int
и начинаются с 1
, не 0
:
class MyClass: Codable {
enum Company: Int {
case toyota = 1
case ford
case gm
}
var company: Company?
Это связано с тем, что значение 0
в соответствующей записи JSON зарезервировано для "не задано"; то есть он должен быть сопоставлен с nil
при настройке инициализации свойства company
из него.
Инициализатор перечисления Swift init?(rawValue:)
предоставляет эту функциональность «из коробки»: аргумент Int
, который не соответствует необработанному значению ни в одном случае, приведет к сбою инициализатора и возвращению nil. Кроме того, перечисления на основе Int
(и String) можно привести в соответствие с Codable
, просто объявив его в определении типа:
enum Company: Int, Codable {
case toyota = 1
case ford
case gm
}
Проблема в том, что мой пользовательский класс имеет более 20 свойств, поэтому я действительно хочу избежать необходимости реализовывать init(from:)
и encode(to:)
, вместо этого полагаясь на автоматическое поведение, полученное путем предоставления пользовательского типа перечисления CondingKeys
.
Это приводит к сбою инициализации всего экземпляра класса, поскольку создается впечатление, что "синтезированный" инициализатор не может сделать вывод о том, что неподдерживаемое необработанное значение типа enum следует рассматривать как nil
(даже если целевое свойство четко обозначено как необязательный, например Company?
).
Я думаю, это так, потому что инициализатор, предоставленный Decodable
, может бросить, но не может вернуть nil:
// This is what we have:
init(from decoder: Decoder) throws
// This is what I would want:
init?(from decoder: Decoder)
В качестве обходного пути я реализовал свой класс следующим образом: сопоставьте целочисленное свойство JSON с частным, хранимым свойством Int
моего класса, которое служит только для хранения, и введите строго типизированное вычисляемое свойство, которое действует как мост между хранилищем и остальной частью моего приложения:
class MyClass {
// (enum definition skipped, see above)
private var companyRawValue: Int = 0
public var company: Company? {
set {
self.companyRawValue = newValue?.rawValue ?? 0
// (sets to 0 if passed nil)
}
get {
return Company(rawValue: companyRawValue)
// (returns nil if raw value is 0)
}
}
enum CodingKeys: String, CodingKey {
case companyRawValue = "company"
}
// etc...
Мой вопрос: есть ли лучший (более простой/элегантный) способ, который:
- не требует дублирования свойств, как мой обходной путь, и
- Не требует ли не полной реализации
init(from:)
и/илиencode(with:)
, возможно, реализации их упрощенных версий, которые по большей части делегируют поведение по умолчанию (т. е. не требуют всего шаблона ручной инициализации/кодирования каждого свойство)?
Дополнение: есть третье, тоже неэлегантное решение, которое не пришло мне в голову, когда я впервые опубликовал вопрос. Он предполагает использование искусственного базового класса только для автоматического декодирования. Я не буду его использовать, а просто опишу здесь для полноты картины:
// Groups all straight-forward decodable properties
//
class BaseClass: Codable {
/*
(Properties go here)
*/
enum CodingKeys: String, CodingKey {
/*
(Coding keys for above properties go here)
*/
}
// (init(from decoder: Decoder) and encode(to:) are
// automatically provided by Swift)
}
// Actually used by the app
//
class MyClass: BaseClass {
enum CodingKeys: String, CodingKey {
case company
}
var company: Company? = nil
override init(from decoder: Decoder) throws {
super.init(from: decoder)
let values = try decoder.container(keyedBy: CodingKeys.self)
if let company = try? values.decode(Company.self, forKey: .company) {
self.company = company
}
}
}
...Но это действительно уродливый хак. Иерархия наследования классов не должна диктоваться такими недостатками.
nil
наself
внутри безотказного инициализатора броска ... Этот ответ присваивает фактическое значение по умолчанию ... - person Nicolas Miari   schedule 06.11.2018nil
моему необязательному свойству, ноDecodable
работает, бросая... - person Nicolas Miari   schedule 06.11.2018class MyClass: Codable { enum Company: Int, Codable { case toyota = 1, ford, gm } var company: Company? required init(from decoder: Decoder) throws { company = try Company(rawValue: decoder.singleValueContainer().decode(Int.self)) } }
- person Leo Dabus   schedule 06.11.2018init(from decoder: Decoder)
); мой класс имеет много свойств помимоcompany
... - person Nicolas Miari   schedule 06.11.2018try? ....
? Вы можете написатьif let propertyName = try? .... { do what you need }
. Вам на самом деле не нужноinit?(from decoder: Decoder)
. - person Lachtan   schedule 04.12.2018