Using Decodable in Swift 4 With Inheritance

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.

how to use inheritance in decodable model

Rather than inheritance and classes use generics and structs because Decodable doesn't support inheritance by default.

For example create a struct JSONParser

struct JSONParser<T : Decodable> {

struct ResponseData<U : Decodable> : Decodable {
let total, count : Int
let results : [U]
}

let code : Int
let status : String
let data : ResponseData<T>

init(data: Data) throws {
let decoder = JSONDecoder()
data = try decoder.decode(ResponseData.self, from: data)
}
}

And use it for the dictionary containing id and title

struct Item {
let id : Int
let title : String
}

do {
let jsonParser = try JSONParser<Item>(data: data)
let results = jsonParser.data.results
} catch { print(error) }

Inheritance of Encodable Class

Encodable and Decodable involve some code synthesis where the compiler essentially writes the code for you. When you conform BasicData to Encodable, these methods are written to the BasicData class and hence they are not aware of any additional properties defined by subclasses. You have to override the encode(to:) method in your subclass:

class AdditionalData: BasicData {
let c: String
let d: Int

init(c: String, d: Int) {
self.c = c
self.d = d
}

private enum CodingKeys: String, CodingKey {
case c, d
}

override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.c, forKey: .c)
try container.encode(self.d, forKey: .d)
}
}

See this question for a similar problem with Decodable.

Swift Attempt to a decode a Codable parent class as one of its subclasses

You need to have a custom init(from:) in Main and decode a to the right subclass directly

class Main: Codable {
let a: A

required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let object = try? container.decode(B1.self, forKey: .a) {
a = object
} else {
a = try container.decode(B2.self, forKey: .a)
}
}
}

Codable with inheritance

If anyone is having the same problem I found below solution.

class DataManager: Codable {
var bases: [Base] = []
init(bases: [Base]) {
self.bases = bases
}
private enum CodingKeys: String, CodingKey {
case bases
}

required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
var list = try container.nestedUnkeyedContainer(forKey: DataManager.CodingKeys.bases)

while !list.isAtEnd {
if let child1 = try? list.decode(Child1.self) {
bases.append(child1 as Base)
} else if let child2 = try? list.decode(Child2.self) {
bases.append(child2 as Base)
}
}
}
}

Is Decodable inheritance even possible in Swift?

This worked:

class Party: Decodable {
var theme: String

}

class PublicParty : Party {
var address: String = ""

required init(from decoder: Decoder) throws {

try super.init(from: decoder)

let values = try decoder.container(keyedBy: CodingKeys.self)
address = try values.decode(String.self, forKey: .address)
}

private enum CodingKeys: String, CodingKey
{
case address

}
}

Codable for class with Inheritance

You should use decodeIfPresent for your optional properties

required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
firstName = try container.decodeIfPresent(String.self, forKey: .firstName)
lastName = try container.decodeIfPresent(String.self, forKey: .lastName)

try super.init(from: decoder)
}

Also note the change to super that was needed to avoid another error. You can also change your catch slightly from

} catch let error as NSError {
print(error)
}

to simply

} catch {
print(error)
}


Related Topics



Leave a reply



Submit