Search code examples
iosswiftcodableencodable

Swift: Using multiple CodingKey types for the same Encodable struct


I'm struggling to write a single function that encodes the following struct:

struct Lookup: Encodable {
    var id: Int
    var name: String
    
    enum StateCodingKeys: String, CodingKey {
        case id = "stateId"
        case name = "stateName"
    }
    
    enum CityCodingKeys: String, CodingKey {
        case id = "cityId"
        case name = "cityName"
    }
    
    func encode(to encoder: Encoder, type: StateCodingKeys.Type) throws {
        var container = encoder.container(keyedBy: type)
        try container.encode(id, forKey: .id)
        try container.encode(name, forKey: .name)
      }
}

the custom encode function here takes StateCodingKeys.Type as a parameter, but I can't find a way to let this function accept any CodingKey type, like the CityCodingKeys enum, is there a way to do that ?


Solution

  • You can create a common protocol for both of your enums, add the enum cases you need as static vars, and conform the enums to the protocol.

    protocol LookupCodingKey: CodingKey {
        static var id: Self { get }
        static var name: Self { get }
    }
    
    enum StateCodingKeys: String, LookupCodingKey {
        case id = "stateId"
        case name = "stateName"
    }
    
    enum CityCodingKeys: String, LookupCodingKey {
        case id = "cityId"
        case name = "cityName"
    }
    

    Then you can add the protocol as a generic constraint:

    func encode<CodingKeyType: LookupCodingKey>(to encoder: Encoder, type: CodingKeyType.Type) throws {
        var container = encoder.container(keyedBy: type)
        try container.encode(id, forKey: .id)
        try container.encode(name, forKey: .name)
    }
    

    Side note:

    If you just want to call encode(to:type:) directly to encode a Lookup, I would suggest that you do not conform to Encodable, since Lookup would have a generated encode(to:) method, that does not call your encode(to:type:).

    When you accidentally pass Lookup to something that expects an Encodable, and that something encodes it using encode(to:), it will have the unexpected keys id and name.

    I haven't tried, but you might be able to conform to EncodableWithConfiguration instead, with the configuration being the type of the coding key.