Hi!
Suppose I have a model like the following that is decoded from JSON:
struct Item: Decodable {
let name: String
let identifier: UUID
}
The JSON can come from various web APIs. While the JSON data from each different place all represent an Item and contain a name and an identifier, they don't always use the same keys.
How would be the best way to decode Items from these different JSON data objects?
Two of the things I've tried are:
Using if statements in init(from:) to choose the right keys:
init(from decoder: Decoder) throws {
if api_alpha {
let container = try decoder.container(keyedBy: AlphaKeys.self)
self.name = try container.decode(String.self, forKey: .name)
...
} else if api_beta {
let container = try decoder.container(keyedBy: BetaKeys.self)
self.name = try container.decode(String.self, forKey: .name)
...
} else if api_gamma ...
...
}
But these becomes unwieldy the more properties the model has/the more APIs there are, plus there is a lot of repetition and every time I an API is added or removed I'd have to update the code.
Create a wrapper around a simple "string key" type, and put the wrapper in the decoder's user info dictionary
struct StringKey: CodingKey {
let stringValue: String
init(stringValue: String) {
self.stringValue = stringValue
}
...
}
struct ItemKeys {
enum Key {
case name, identifier
}
let dictionary: [Key: StringKey]
subscript(_ key: Key) -> StringKey {
return dictionary[key]!
}
}
struct Item: Decodable {
...
init(from decoder: Decoder) throws {
let codingKeys = decoder.userInfo[wrapperKey] as! ItemKeys
let container = try decoder.container(keyedBy: StringKey.self)
self.name = container.decode(String.self, forKey: codingKeys[.name])
...
}
}
This works fine, but the compiler can't check if a key is missing from the wrapper, so I have to do it myself.
Is there a better/standard way to do solve this problem?
With Swift 5.3 and SE-0280 I thought that maybe there's now someway of doing something like this:
protocol ItemCodingKey: CodingKey {
static var name: Self { get }
...
}
enum AlphaItemKeys: String, ItemCodingKey {
case name = "name"
}
enum BetaItemKeys: String, ItemCodingKey {
case name = "n"
}
...
let decoderForDecodingFromAlpha = JSONDecoder()
decoderForDecodingFromAlpha.userInfo[userInfoKey] = AlphaItemsKeys.self
let decoderForDecodingFromBeta = JSONDecoder()
decoderForDecodingFromBeta.userInfo[userInfoKey] = BetaItemsKeys.self
But since the ItemCodingKeys has Self requirements, I'm not sure how I'd get it back out from the user info dictionary.