Using Decodable in Swift 4 with Inheritance
I believe in the case of inheritance you must implement Coding
yourself. That is, you must specify CodingKeys
and implement init(from:)
and encode(to:)
in both superclass and subclass. Per the WWDC video (around 49:28, pictured below), you must call super with the super encoder/decoder.
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)}
The video seems to stop short of showing the encoding side (but it's container.superEncoder()
for the encode(to:)
side) but it works in much the same way in your encode(to:)
implementation. I can confirm this works in this simple case (see playground code below).
I'm still struggling with some odd behavior myself with a much more complex model I'm converting from NSCoding
, which has lots of newly-nested types (including struct
and enum
) that's exhibiting this unexpected nil
behavior and "shouldn't be". Just be aware there may be edge cases that involve nested types.
Edit: Nested types seem to work fine in my test playground; I now suspect something wrong with self-referencing classes (think children of tree nodes) with a collection of itself that also contains instances of that class' various subclasses. A test of a simple self-referencing class decodes fine (that is, no subclasses) so I'm now focusing my efforts on why the subclasses case fails.
Update June 25 '17: I ended up filing a bug with Apple about this. rdar://32911973 - Unfortunately an encode/decode cycle of an array of Superclass
that contains Subclass: Superclass
elements will result in all elements in the array being decoded as Superclass
(the subclass' init(from:)
is never called, resulting in data loss or worse).
//: Fully-Implemented Inheritanceclass 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 superencoder = container.superEncoder() try super.encode(to: superencoder) }}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)
Both the super- and subclass properties are restored in fullSubDecoded
.
Found This Link - Go down to inheritance section
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)}
For Decoding I did this:
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}
🚀 Swift introduced Property Wrappers in 5.1 I implemented a library called SerializedSwift that uses the power of property wrappers to Decode and Encode JSON data to objects.
One of my main goals was, to make inherited object to decode out of the box, without additonal init(from decoder: Decoder)
overrides.
import SerializedSwiftclass User: Serializable { @Serialized var name: String @Serialized("globalId") var id: String? @Serialized(alternateKey: "mobileNumber") var phoneNumber: String? @Serialized(default: 0) var score: Int required init() {}}// Inherited objectclass PowerUser: User { @Serialized var powerName: String? @Serialized(default: 0) var credit: Int}
It also supports custom coding keys, alternate keys, default values, custom transformation classes and many more features to be included in the future.