Problem
With Codable types Swift, a Dictionary is only encoded to a map (as JSON), e.g. { "foo": "my value" }
when it's key type is either a String
or an Int
. In all other cases, the encoding is an array where the keys and values are interspersed, e.g. ["key1", "value1", "key2", "value2"]
This is somewhat reasonable, but, it would be really handy when the Key is RawRepresentable
and RawValue == String
or RawValue == Int
it would also be encoded to a map object instead of an array.
Ideal Solution
I guess ideally, maybe this is a topic for Swift Evolution? But, failing that, it would be really handy if MetaCodable could provide macro to achieve this. Perhaps something like: @CodedBy(.dictionary)
?
Alternative
I think the alternative is to write a full-blown custom encoder. I did look at trying to make Dictionary
conform to HelperCoder
but ran into public
/package
issues. Although I did get this to work...
struct DictionaryCoder<Key: Hashable & RawRepresentable, Value: Codable>: HelperCoder
where Key.RawValue == String {
func decode(from decoder: any Decoder) throws -> Dictionary<Key, Value> {
let container = try decoder.singleValueContainer()
let dictionary = try container.decode([Key.RawValue: Value].self)
var output = Dictionary<Key, Value>()
for (rawValue, value) in dictionary {
if let key = Key(rawValue: rawValue) {
output[key] = value
}
}
return output
}
func encode(_ value: Dictionary<Key, Value>, to encoder: any Encoder) throws {
let dictionary: [String: Value] = Dictionary(
uniqueKeysWithValues: value.map { ($0.rawValue, $1) }
)
var container = encoder.singleValueContainer()
try container.encode(dictionary)
}
}
extension Dictionary where Key: Hashable & RawRepresentable, Key.RawValue == String, Value: Codable {
static var coder: DictionaryCoder<Key, Value> {
DictionaryCoder<Key, Value>()
}
}
which would then be used as...
@CodedBy([MyRawRepresentableKey: MyValue].coder)
let valuesByKey: [MyRawRepresentableKey: MyValue]
but this is far from ideal, not only does it have some edges cases but the ergonomics are poor. It would be better if the helper coder is generated by the CodedBy
macro itself, because it has knowledge of the Coded
type.
Thoughts?