Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Make class conforming to Codable by default in Swift

Such feature of Swift as Codable (Decodable&Encodable) protocol is very useful. But I found such issue: Let we have class Parent conforming to Codable:

class Parent: Codable {
    var name: String
    var email: String?
    var password: String?
}

Ok, that class is conforming to Codable protocol "from the box", you don't need write any initializers, it's ready to be initialized from JSON like that:

{ "name": "John", "email": "[email protected]", "password": <null>}

But let's say we need other class, Child inherits from Parent and be conforming to Codable:

class Child: Parent {
   var token: String
   var date: Date?
}

so class Child must be conforming to Codable by conformance to Parent, BUT properties of class Child won't be initialized from JSON properly. Decision I found is write all Codable stuff for class Child by myself, like:

class Child: Parent {
    var token: String
    var date : Date?

    enum ChildKeys: CodingKey {
        case token, date
    }

    required init(from decoder: Decoder) throws {
        try super.init(from: decoder)
        let container = try decoder.container(keyedBy: ChildKeys.self)
        self.token = try container.decode(String.self, forKey: .token)
        self.date = try container.decodeIfPresent(Date.self, forKey: .date)
    }

    override func encode(to encoder: Encoder) throws {
        try super.encode(to: encoder)
        var container = encoder.container(keyedBy: ChildKeys.self)
        try container.encode(self.token, forKey: .token)
        try container.encodeIfPresent(self.date, forKey: .date)
    }
}

But I feel it can't be right, did I missed something? How to make class Child conforming to Codable properly without writing all that stuff?

like image 455
zzheads Avatar asked Oct 28 '25 09:10

zzheads


1 Answers

Here's a good blog post which includes an answer to your question: source

Scroll down to inheritance and you'll see the following:

Assuming we have the following classes:

class Person : Codable {
    var name: String?
}

class Employee : Person {
    var employeeID: String?
}

We get the Codable conformance by inheriting from the Person class, but what happens if we try to encode an instance of Employee?

let employee = Employee()
employee.employeeID = "emp123"
employee.name = "Joe"

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try! encoder.encode(employee)
print(String(data: data, encoding: .utf8)!)

{
  "name" : "Joe"
}

This is not the expected result, so we have to add a custom implementation like this:

class Person : Codable {
    var name: String?

    private enum CodingKeys : String, CodingKey {
        case name
    }

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

Same story for the subclass:

class Employee : Person {
    var employeeID: String?

    private enum CodingKeys : String, CodingKey {
        case employeeID = "emp_id"
    }

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

The result would be:

{
  "emp_id" : "emp123"
}

Which again is not the expected result, so here we are using inheritance by calling super.

// Employee.swift
    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)
    }

Which finally gives us what we really wanted from the beginning:

{
    "name": "Joe",
    "emp_id": "emp123"
}

If you're not happy with the flattened result, there's a tip on how to avoid that too.

All the credits to the guy who wrote the blog post and my thanks. Hope it helps you as well, cheers!

like image 154
Mihai Erős Avatar answered Oct 29 '25 23:10

Mihai Erős



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!