Using "Codable" to Set Property Values Doesn't Work Through Inheritance

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 only status
  • decoder.decode(LoginResponse.self ... decodes status and message

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



Leave a reply



Submit