Search code examples
swiftprotocolscodabledecodableencodable

Codable variable inside Codable struct causes error


Please take a look at my code:

No errors

struct Person: Codable {
    var name: String
    var age: Double
    var birthday: Date
    
    var selectedItem: Car
}

struct Car: Codable {
    var companyName: String
    var creationDate: Date
}

struct iPhone: Codable {
    var model: String
    var creationDate: Date
    var isScreenBroken: Bool = true
}

Build error

struct Person: Codable { // "Type 'Person' does not conform to protocol 'Decodable'", "Type 'Person' does not conform to protocol 'Encodable'"
    var name: String
    var age: Double
    var birthday: Date
    
    var selectedItem: Codable // I've changed this line
}

struct Car: Codable {
    var companyName: String
    var creationDate: Date
}

struct iPhone: Codable {
    var model: String
    var creationDate: Date
    var isScreenBroken: Bool = true
}

Type 'Person' does not conform to protocol 'Decodable'

Type 'Person' does not conform to protocol 'Encodable'

I don't understand why is this happening. It knows that selectedItem is conforming to Encodable & Decodable:

var selectedItem: Codable

I'm new to protocols in Swift so please when answering try to explain what's happening here.

Thanks!


Solution

  • The problem for the compiler here is that normally when a type is defined to conform to Codable then the compiler synthesises code for you to make Person in this case conform to the protocol. It does this by creating an implementation of init(from decoder: Decoder) throws and one of func encode(to encoder: Encoder) throws for you.

    But when you change selectedItem to be of type Codable then the compiler can no longer synthesise these methods since it needs to know exactly what properties the type of selectedItem has to correctly generate the code.

    What you need to do here is to use generics

    struct Person<T: Codable>: Codable {
        var name: String
        var age: Double
        var birthday: Date
    
        var selectedItem: T
    }
    
    struct Car: Codable {
        var companyName: String
        var creationDate: Date
    }
    
    struct iPhone: Codable {
        var model: String
        var creationDate: Date
        var isScreenBroken: Bool = true
    }
    

    Then the compiler is happy again and you can use it like

    let person = Person(name: "Joe", age: 40, birthday: date, selectedItem: Car(companyName: "Ford", creationDate: Date()))