Using Codable to set property values doesn't work through inheritance
The magic of Codable
relies on simplicity (using structs which don't support inheritance).
The more custom, the more code
You have to write a custom initializer in the subclass to consider inheritance (thanks to Hamish for the note that CodingKeys and initializer are synthesized in the base class), I omitted the Encodable
part intentionally
class Primary : Decodable {
var a: Int
/*
private enum CodingKeys: String, CodingKey { case a }
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
a = try container.decode(Int.self, forKey: .a)
}
*/
}
class Secondary : Primary {
var b: Int
private enum CodingKeys: String, CodingKey { case b }
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
b = try container.decode(Int.self, forKey: .b)
try super.init(from: decoder)
}
}
func testRef<T: Decodable>() throws -> T {
let json = "{\"a\":5, \"b\" : 10}".data(using: .utf8)!
return try JSONDecoder().decode(T.self, from: json)
}
do {
let secondary : Secondary = try testRef()
print(secondary.a, secondary.b) // 5 10
} catch { print(error) }
Using Decodable with inheritance raises an exception
There is no need to use the superDecoder
, you can simply do this (I changed the variable names to lowercase to conform to the naming convention)
class LoginResponse: BaseResponse {
let message: String
private enum CodingKeys: String, CodingKey{
case message = "Message"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
message = try container.decode(String.self, forKey: .message)
try super.init(from: decoder)
}
}
class BaseResponse: Decodable {
let status: Int
private enum CodingKeys: String, CodingKey{
case status = "Status"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
status = try container.decode(Int.self, forKey: .status)
}
}
decoder.decode(BaseResponse.self ...
decodes onlystatus
decoder.decode(LoginResponse.self ...
decodesstatus
andmessage
And never en-/decode with try!
. Handle the error.
Why inherited class can't be json encode
It's not difficult:
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(Password, forKey: .Password)
try super.encode(to: encoder)
}
No need to create nested encoders.
It works the same with decoding:
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
Password = try container.decode(String.self, forKey: .Password)
try super.init(from: decoder)
}
How to add custom transient property in Codable struct
If you don't want to decode those 4 properties, just don't include them in the CodingKeys
(and don't forget to explicitly provide default values for them, so that the decoder can initialize the object properly):
struct VideoAlbum: Codable {
let id, image: String?
let video, mediaType: JSONNull?
let type, deleted, createdOn: String?
let modifiedOn: JSONNull?
var isPlaying: Bool? = nil
var currentTime: CMTime? = nil
var timeObserver: Any? = nil
var pausedByUser: Bool? = nil
enum CodingKeys: String, CodingKey {
// include only those that you want to decode/encode
case id, image, video
case mediaType = "media_type"
case type, deleted
case createdOn = "created_on"
case modifiedOn = "modified_on"
}
}
Is it possible to use Codable subclasses without explicitly decoding?
What you are asking for is compiler synthesis of an override of init(from:)
in Beer
. Swift doesn't do that. This has been discussed on the Swift forum, for example in this thread. Itai Ferber is the primary Apple engineer responsible for the design and implementation of Codable
so you can consider his response authoritative.
If you remove the Codable
conformance from Beverage
and add it to Beer
directly (and to any other leaf subclasses of Beverage
) then you might get the behavior you want. That will only work if you don't need the conformance on Beverage
itself though.
Swift Codable struct recursively containing itself as property
A simple way is to just change the struct into a class:
class Message: Codable {
let content: String
// ...other values
let reference: Message? // optional - the recursion has to end somewhere right?
}
But this could break other parts of your code, since structs and classes have vastly different semantics.
An alternative would be to make a reference type Box
:
class Box<T: Codable>: Codable {
let wrappedValue: T
required init(from decoder: Decoder) throws {
wrappedValue = try T(from: decoder)
}
func encode(to encoder: Encoder) throws {
try wrappedValue.encode(to: encoder)
}
}
Then,
struct Message: Codable {
let content: String
// ...other values
let boxedReference: Box<Message>?
// you can still refer to 'reference' as 'reference' in your swift code
var reference: Message? { boxedReference?.wrappedValue }
enum CodingKeys: String, CodingKey {
case content, boxedReference = "reference"
}
}
enum encoded value is nil while storing the class object in UserDefaults. Codable Protocol is already inherited
You should include userType
in your CodingKeys
enum:
enum CodingKeys: String, CodingKey {
case userFullName
case userType
}
Or just delete the CodingKeys
enum entirely, since by default, all the properties are included as coding keys. The keys in the CodingKeys
enum determines what the synthesised Codable
implementation will encode and decode. If you don't include userType
, userType
will not be encoded, so it will not be stored into UserDefaults
.
I am not getting it from Server and userType is an external property outside the JSON response
This is fine, because userType
is optional. If the JSON does not have the key, it will be assigned nil
. This might be a problem if you are also encoding User
and sending it to the server, and that the server can't handle extra keys in the request, in which case you need two structs - one for storing to/loading from UserDefaults
, one for parsing/encoding server response/request.
Remember to encode a new User
to UserDefaults
when you try this out, since the old one still doesn't have the userType
encoded with it.
Ignoring superclass property while decoding subclass
A quick hack is to declare it as optional. For instance:
class FirstClas: Codable {
let firstClassProperty: Int
final let arrayOfInts: [Int]?
}
That would work around a missing arrayOfInts
automatically.
Manual solution. Another solution is to implement the Decodable
protocol yourself — as you did in SecondClass
— and decode arrayOfInts
using decodeIfPresent
(and use a default value otherwise).
Superclass decoding. By the way, the recommended way to forward the Decoder
to the superclass is by using superDecoder()
method:
...
let superDecoder = try container.superDecoder()
try super.init(from: superDecoder)
Related Topics
Adding Elements on Many-To-Many Relation
Attributed Text, Replace a Specific Font by Another Using Swift
Why Is There a "Plus" Icon at the Top Right Corner of My View
How to Set Width and Height of an Image in Swiftui
How to Display Current Time (Realtime) in iOS 14 Home Widget
JSONencoder and Propertylistencoder Don't Conform to Encoder
Observe Progress of Data Download in Swift
How to Use Bit Field with Swift to Store Values with More Than 1 Bit
Firestore - How to Get Around Array "Does-Not-Contain" Queries
What's the Advantage of #Selector Update in Swift 2.3
Does Nsnumberformatter.Stringfromnumber Ever Return Nil
How Have Multiple Init() with Swift
Cursor Shifts to End on Edit of Formatted Decimal Textfield - Swift
Swiftui Casting Tupleview to an Array of Anyview
Testing If a Decimal Is a Whole Number in Swift
How to Initialize a Mlmultiarray in Coreml
How to Tell If a Symbolic Link Exists at a Certain Path
How to Resolve Error in Unit Testing When We Have Date Comparison in Codable