Использование Decodable в Swift 4 с наследованием
Если использование наследования класса нарушает Декодируемость класса. Например, следующий код
class Server : Codable {
var id : Int?
}
class Development : Server {
var name : String?
var userId : Int?
}
var json = "{\"id\" : 1,\"name\" : \"Large Building Development\"}"
let jsonDecoder = JSONDecoder()
let item = try jsonDecoder.decode(Development.self, from:json.data(using: .utf8)!) as Development
print(item.id ?? "id is nil")
print(item.name ?? "name is nil") here
:
1
name is nil
Теперь, если я отменил это, имя декодирует, но id не делает.
class Server {
var id : Int?
}
class Development : Server, Codable {
var name : String?
var userId : Int?
}
var json = "{\"id\" : 1,\"name\" : \"Large Building Development\"}"
let jsonDecoder = JSONDecoder()
let item = try jsonDecoder.decode(Development.self, from:json.data(using: .utf8)!) as Development
print(item.id ?? "id is nil")
print(item.name ?? "name is nil")
:
id is nil
Large Building Development
И вы не можете выразить Codable в обоих классах.
Ответы
Ответ 1
Я считаю, что в случае наследования вы должны внедрить Coding
самостоятельно. То есть вы должны указать CodingKeys
и реализовать init(from:)
и encode(to:)
как в суперклассе, так и в подклассе. В соответствии с видео WWDC (около 49:28, на рисунке ниже), вы должны вызывать super с помощью супер-кодера/декодера.
![WWDC 2017 Session 212 Screenshot at 49:28 (Source Code)]()
required init(from decoder: Decoder) throws {
// Get our container for this subclass' coding keys
let container = try decoder.container(keyedBy: CodingKeys.self)
myVar = try container.decode(MyType.self, forKey: .myVar)
// otherVar = ...
// Get superDecoder for superclass and call super.init(from:) with it
let superDecoder = try container.superDecoder()
try super.init(from: superDecoder)
}
Видео, кажется, остановится, показывающая сторона кодирования (но container.superEncoder()
для encode(to:)
сторону), но она работает во многом так же, как в вашем encode(to:)
реализации. Я могу подтвердить, что это работает в этом простом случае (см. Код игровой площадки ниже).
Я до сих пор борюсь с каким-то странным поведением и с гораздо более сложной моделью, которую я конвертирую из NSCoding
, которая имеет много новых вложенных типов (включая struct
и enum
), которые демонстрируют это неожиданное поведение nil
и "не должно быть", Просто знайте, что могут быть крайние случаи, которые включают вложенные типы.
Редактировать: Вложенные типы, кажется, отлично работают на моей тестовой площадке; Теперь я подозреваю, что что-то не так с самоссылающимися классами (например, потомками узлов дерева) с собственной коллекцией, которая также содержит экземпляры различных подклассов этого класса. Тест простого самоссылающегося класса прекрасно декодирует (то есть не имеет подклассов), поэтому я сейчас сосредотачиваю свои усилия на том, почему случай с подклассами терпит неудачу.
Обновление от 25 июня 17 года: я закончил с сообщением об ошибке Apple. rdar://32911973 - К сожалению, цикл кодирования/декодирования массива Superclass
который содержит элементы Subclass: Superclass
, приведет к тому, что все элементы в массиве будут декодированы как Superclass
(init(from:)
подкласса init(from:)
) никогда не вызывается, что приводит к потеря данных или хуже).
//: Fully-Implemented Inheritance
class FullSuper: Codable {
var id: UUID?
init() {}
private enum CodingKeys: String, CodingKey { case id }
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(UUID.self, forKey: .id)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
}
}
class FullSub: FullSuper {
var string: String?
private enum CodingKeys: String, CodingKey { case string }
override init() { super.init() }
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let superdecoder = try container.superDecoder()
try super.init(from: superdecoder)
string = try container.decode(String.self, forKey: .string)
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(string, forKey: .string)
let superdecoder = container.superEncoder()
try super.encode(to: superdecoder)
}
}
let fullSub = FullSub()
fullSub.id = UUID()
fullSub.string = "FullSub"
let fullEncoder = PropertyListEncoder()
let fullData = try fullEncoder.encode(fullSub)
let fullDecoder = PropertyListDecoder()
let fullSubDecoded: FullSub = try fullDecoder.decode(FullSub.self, from: fullData)
Свойства super- и подкласса восстанавливаются в fullSubDecoded
.
Ответ 2
Найти эту ссылку - перейти в раздел наследования
override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(employeeID, forKey: .employeeID)
}
Для декодирования я сделал это:
required init(from decoder: Decoder) throws {
try super.init(from: decoder)
let values = try decoder.container(keyedBy: CodingKeys.self)
total = try values.decode(Int.self, forKey: .total)
}
private enum CodingKeys: String, CodingKey
{
case total
}
Ответ 3
Я был в состоянии сделать его работу, сделав свой базовый класс и подклассы соответствуют Decodable
вместо Codable
. Если бы я использовал Codable
он Codable
бы странным образом, например, получая EXC_BAD_ACCESS
при доступе к полю подкласса, но отладчик мог без проблем отображать все значения подкласса.
Кроме того, передача superDecoder в базовый класс в super.init()
не сработала. Я только что передал декодер из подкласса в базовый класс.
Ответ 4
Как насчет использования следующего способа?
protocol Parent: Codable {
var inheritedProp: Int? {get set}
}
struct Child: Parent {
var inheritedProp: Int?
var title: String?
enum CodingKeys: String, CodingKey {
case inheritedProp = "inherited_prop"
case title = "short_title"
}
}
Дополнительная информация о композиции: http://mikebuss.com/2016/01/10/interfaces-vs-inheritance/
Ответ 5
Вот библиотека TypePreservingCodingAdapter для этого (может быть установлена с Cocoapods или SwiftPackageManager).
Приведенный ниже код компилируется и прекрасно работает с Swift 4.2
. К сожалению, для каждого подкласса вам нужно самостоятельно реализовать кодирование и декодирование свойств.
import TypePreservingCodingAdapter
import Foundation
// redeclared your types with initializers
class Server: Codable {
var id: Int?
init(id: Int?) {
self.id = id
}
}
class Development: Server {
var name: String?
var userId: Int?
private enum CodingKeys: String, CodingKey {
case name
case userId
}
init(id: Int?, name: String?, userId: Int?) {
self.name = name
self.userId = userId
super.init(id: id)
}
required init(from decoder: Decoder) throws {
try super.init(from: decoder)
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decodeIfPresent(String.self, forKey: .name)
userId = try container.decodeIfPresent(Int.self, forKey: .userId)
}
override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(userId, forKey: .userId)
}
}
// create and adapter
let adapter = TypePreservingCodingAdapter()
let encoder = JSONEncoder()
let decoder = JSONDecoder()
// inject it into encoder and decoder
encoder.userInfo[.typePreservingAdapter] = adapter
decoder.userInfo[.typePreservingAdapter] = adapter
// register your types with adapter
adapter.register(type: Server.self).register(type: Development.self)
let server = Server(id: 1)
let development = Development(id: 2, name: "dev", userId: 42)
let servers: [Server] = [server, development]
// wrap specific object with Wrap helper object
let data = try! encoder.encode(servers.map { Wrap(wrapped: $0) })
// decode object back and unwrap them force casting to a common ancestor type
let decodedServers = try! decoder.decode([Wrap].self, from: data).map { $0.wrapped as! Server }
// check that decoded object are of correct types
print(decodedServers.first is Server) // prints true
print(decodedServers.last is Development) // prints true