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 var
s, 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)
}
}