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 Item
s 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.