In a couple of apps that I've been working on lately, I need to read a list of items of mixed types. A good example of a "real-life" situation would be a list of items in an article: there is a title, a subtitle, body text, images etc.
I've come up with something like this:
// Sample data:
//
// {
// items: [
// {
// "type": "a",
// "a": "This one is A"
// },
// {
// "type": "b",
// "b": "This one is B"
// }
// ]
// }
import Foundation
// MARK: - "Root" protocol for all decodable items
protocol Item: Decodable {}
// MARK: - Items
struct ItemA: Item { let a: String }
struct ItemB: Item { let b: String }
// MARK: - Extractor
/// Helper for extracting items
struct ItemExtractor: Decodable {
enum TypeKey: String, Decodable {
case a
case b
}
let item: Item
// MARK: - Decodable compliance
enum CodingKeys: String, CodingKey {
case type
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let type = try values.decode(TypeKey.self, forKey: .type)
let itemValues = try decoder.singleValueContainer()
// In particular, looking to eliminate this `switch`
switch type {
case .a:
item = try itemValues.decode(ItemA.self)
case .b:
item = try itemValues.decode(ItemB.self)
}
}
}
// MARK: - Container
/// Container of items
struct MyList: Decodable {
let items: [Item]
// MARK: - Decodable compliance
enum CodingKeys: String, CodingKey {
case items
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let extractors = try values.decode([ItemExtractor].self, forKey: .items)
items = extractors.map { $0.item }
}
}
- The type of an item in the data file is marked up with a "type" field;
- The ItemExtractor helper reads the type and inits an item according to it's type;
- Those items are then taken from the ItemExtractor array and added to the list of items in the MyList struct.
Now the question: is there a way to generalize this code to avoid the switch statement in the ItemExtractor, which would be especially useful if there is a lot of different types of items, or, perhaps, there is a different approach altogether?
Looking forward for a better solution, –Baglan