Skip to content

Instantly share code, notes, and snippets.

@michaelbiggs
Forked from cliss/Example.swift
Last active February 3, 2023 17:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save michaelbiggs/e1c09cbf6d78a0e9f7b32277fa8cda07 to your computer and use it in GitHub Desktop.
Save michaelbiggs/e1c09cbf6d78a0e9f7b32277fa8cda07 to your computer and use it in GitHub Desktop.
Decoding a heterogenous JSON array with unknown object types
// 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"
},
{
"itemType": "dessert",
"entreeName": "Crème Brûlée"
}
]
}
"""
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
case unknown
init(from decoder: Decoder) throws {
self = try MenuItemType(rawValue: decoder.singleValueContainer().decode(RawValue.self)) ?? .unknown
}
}
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)
case .unknown:
let itemTypeString = try menuItem.decode(String.self, forKey: .itemType)
print("Unknown menu item type: \(itemTypeString)\n")
}
}
// 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
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment