Home > Enterprise >  Swift: Using multiple CodingKey types for the same Encodable struct
Swift: Using multiple CodingKey types for the same Encodable struct

Time:10-18

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 ?

CodePudding user response:

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.

CodePudding user response:

Tough to tell what you re trying to do but you can always create two types that conform to Lookup. There are many benefits to this setup the most important being that you can tell which type you are dealing with later.

///Common protocol for all items that will have loop up properties
protocol Lookup: Encodable {
    var id: Int {get set}
    var name: String {get set}
    
}
///Type for the State's
struct StateLookup: Lookup{
    var id: Int
    var name: String
    
    enum StateCodingKeys: String, CodingKey {
        case id = "stateId"
        case name = "stateName"
    }
}
///Type for the city's
struct CityLookup: Lookup{
    var id: Int
    var name: String
    
    enum CityCodingKeys: String, CodingKey {
        case id = "cityId"
        case name = "cityName"
    }
}
///Items can be stored in a common array/sequence
let items: [any Lookup] = [StateLookup(id: 1, name: "state"), CityLookup(id: 2, name: "city")]

///You can identify the type later easily.
func identify(items: [any Lookup]){
    for item in items {
        if item is StateLookup{
            print("item is a state")
        } else if item is CityLookup{
            print("item is a city")
        } else {
            print("unknown lookup type")
        }
    }
}

///Since they can be stored together they can be filtered together too.
func filter(items: [any Lookup], searchString: String) -> [any Lookup]{
    items.filter { item in
        item.name.localizedCaseInsensitiveContains(searchString)
    }
}
  • Related