Skip to content

Instantly share code, notes, and snippets.

@cliss
Last active February 6, 2023 12:31
Show Gist options
  • Select an option

  • Save cliss/c4c788b6cee2a8f3d307b965c33cdc4c to your computer and use it in GitHub Desktop.

Select an option

Save cliss/c4c788b6cee2a8f3d307b965c33cdc4c to your computer and use it in GitHub Desktop.

Revisions

  1. cliss revised this gist Feb 2, 2023. 1 changed file with 0 additions and 1 deletion.
    1 change: 0 additions & 1 deletion Example.swift
    Original file line number Diff line number Diff line change
    @@ -2,7 +2,6 @@

    import Foundation

    var greeting = "Hello, playground"
    let json =
    """
    {
  2. cliss created this gist Feb 2, 2023.
    146 changes: 146 additions & 0 deletions Example.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,146 @@
    // This is intended to be dropped in a Playground.

    import Foundation

    var greeting = "Hello, playground"
    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
    */