Swift Decodable for a list or items of mixed types

153 views Asked by At

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

0

There are 0 answers