// This is intended to be dropped in a Playground. import Foundation let json = """ { "name": "Casey's Corner", "menu": [ { "itemType": "drink", "drinkName": "Dry Vodka Martini" }, { "itemType": "drink", "drinkName": "Jack-and-Diet" }, { "itemType": "appetizer", "appName": "Nachos" }, { "itemType": "entree", "entreeName": "Steak", "temperature": "Medium Rare" }, { "itemType": "entree", "entreeName": "Caesar Salad" }, { "itemType": "entree", "entreeName": "Grilled Salmon" } ] } """ struct Drink: Decodable { let drinkName: String } struct Appetizer: Decodable { let appName: String } struct Entree: Decodable { let entreeName: String let temperature: String? } struct Restaurant: Decodable { let name: String let menu: [Any] // The normal, expected CodingKey definition for this type enum RestaurantKeys: CodingKey { case name case menu } // The key we use to decode each menu item's type enum MenuItemTypeKey: CodingKey { case itemType } // The enumeration that actually matches menu item types; // note this is **not** a CodingKey enum MenuItemType: String, Decodable { case drink case appetizer case entree } init(from decoder: Decoder) throws { // Get the decoder for the top-level object let container = try decoder.container(keyedBy: RestaurantKeys.self) // Decode the easy stuff: the restaurant's name self.name = try container.decode(String.self, forKey: .name) // Create a place to store our menu var inProgressMenu: [Any] = [] // Get a copy of the array for the purposes of reading the type var arrayForType = try container.nestedUnkeyedContainer(forKey: .menu) // Make a copy of this for reading the actual menu items. var array = arrayForType // Start reading the menu array while !arrayForType.isAtEnd { // Get the object that represents this menu item let menuItem = try arrayForType.nestedContainer(keyedBy: MenuItemTypeKey.self) // Get the type from this menu item let type = try menuItem.decode(MenuItemType.self, forKey: .itemType) // Based on the type, create the appropriate menu item // Note we're switching to using `array` rather than `arrayForType` // because we need our place in the JSON to be back before we started // reading this menu item. switch type { case .drink: let drink = try array.decode(Drink.self) inProgressMenu.append(drink) case .appetizer: let appetizer = try array.decode(Appetizer.self) inProgressMenu.append(appetizer) case .entree: let entree = try array.decode(Entree.self) inProgressMenu.append(entree) } } // Set our menu self.menu = inProgressMenu } } let data = json.data(using: .utf8)! let restaurant = try! JSONDecoder().decode(Restaurant.self, from: data) print("\(restaurant.name)") for item in restaurant.menu { if let d = item as? Drink { print(" +-- Drink: \(d.drinkName)") } else if let a = item as? Appetizer { print(" +-- Appetizer: \(a.appName)") } else if let e = item as? Entree { print(" +-- Entree: \(e.entreeName)") if let temp = e.temperature { print(" Temperature: \(temp)") } } } /* Expected output: * Casey's Corner * +-- Drink: Dry Vodka Martini * +-- Drink: Jack-and-Diet * +-- Appetizer: Nachos * +-- Entree: Steak * Temperature: Medium Rare * +-- Entree: Caesar Salad * +-- Entree: Grilled Salmon */