Easy JSON Parsing - 5 Steps to Generate Data Models with Xcode Playgrounds and Codable
Aug 13, 2018
5 Steps to Generate Data Models with Xcode Playgrounds and Codable

Dealing with JSON is one of those tasks that almost every developer has to perform in order to construct data models with which to populate project views. However, building and testing a good data model can be time-consuming and prone to mistakes.

JSON Parsing in Swift Before Codable

Prior to Swift 4, Swift's Foundation library provided native JSON parsing by way of the JSONSerialization API. Simply put, JSONSerialization would convert an API payload into a collection of dictionaries with nested arrays. However, though converting the JSON payload to a Swift data structure was relatively painless, accessing the nested data was anything but.

For example:

let stringToReturn = """
    {
        "villains":{
          "Darth Vader":{
             "allegiance":"Dark Side",
             "ice cream":"chocolate"
          },
          "The Emperor":{
             "allegiance":"Dark Side",
             "ice cream":"strawberry"
          },
          "Boba Fett":{
             "allegiance":"Dark Side",
             "ice cream":"rum-and-raisin"
          }
        }
    }
    """

    let jsonPayload = stringToReturn.data(using: String.Encoding.utf8, allowLossyConversion: false)!

    do {
        let json = try JSONSerialization.jsonObject(with: jsonPayload, options: [JSONSerialization.ReadingOptions.mutableContainers]) as! [String: AnyObject]

        if let villains = json["villains"] {
            if let darthVader = villains["Darth Vader"] as? [String: String] {
                if let iceCreamFlavor = darthVader["ice cream"] {
                    print("Darth Vader considers \(iceCreamFlavor) ice cream as the one true ice cream.")
                }
            }

        }

    } catch let error as Error {
        print ("We encountered an error: \(error.localizedDescription)")
    }

For anything more complicated than the simple example above (think nested arrays of arrays), accessing keys and values quickly grew into the ‘if let’ Pyramid of Doom. Angelo Villegas provides a good example of the Pyramid of Doom and its attendant rightward drift in this article. Third-party frameworks, such as SwiftyJSON, ameliorated some of this pain, but it meant bloating your binary with a third-party framework. Using multiple optional bindings offered a better alternative than a cascade of nested if let statements, but it was still a lot of work to parse anything but the simplest of JSON payloads.

Enter Codable

In September, 2017, the Swift Core Team released Swift 4.0. Among many enhancements, the Foundation library received the new Codable protocol. To paraphrase Apple’s documentation on the topic, Codable is a protocol that allows developers to encode and decode their conforming data types to be compatible with external representations of their data such as JSON. An in-depth discussion of Codable is beyond the scope of this post, but I encourage you to have a look at Ben Scheirman’s Ultimate Guide to JSON Parsing with Swift 4 to read about the benefits Codable offers. In short, Codable significantly reduces the overhead of unpacking JSON payloads for developers and does away with the need for optional bindings.

However, even with the convenience and ease of use Codable offers, mapping a new API to a Swift model can be time-consuming. Below, I’ll show you a quick and effortless way to get your data modeling out of the way so you can get on with actually using the data in your project.

Data Modeling with Xcode Playgrounds

Xcode Playgrounds allows us to quickly build and test data models without having to set up a full Xcode project and make countless API calls to our endpoint. For our purposes, we’ll use SpaceX’s API for rocket data.

  • 1. Open Xcode Playgrounds

  • 2. Open your browser of choice and enter https://api.spacexdata.com/v2/rockets

  • 3. Select and copy the resulting JSON

  • 4. Go to jsonprettyprint.com to prettify the JSON string*

  • 5. Open the side pane in Xcode Playgrounds

  • 6. Right click the 'Sources' folder and select 'New File'

  • 7. Name it Rockets.json

  • 8. Paste the prettified JSON into this file and save

  • 9. Add another new file to the 'Sources' folder and name it RocketModel.swift

  • 10. For our purposes, we’ll use a struct to model our rocket data. In RocketModel.swift, paste the following**:

  • public typealias Rockets = [RocketModel]
    
    public struct Height: Codable {
        public let meters: Int
        public let feet: Double
    }
    
    public struct RocketModel: Codable {
    
        // IMPORTANT: The variable names here must match those in the CodingKeys enum
        public let id: String
        public let name: String
        public let type: String
        public let active: Bool
        public let stages: Int
        public let boosters: Int
        public let costPerLaunch: Int
        public let successRate: Int
        public let firstFlight: String
        public let country: String
        public let company: String
        public let height: Height
        public let description: String
    
        enum CodingKeys: String, CodingKey {
            case id
            case name
            case type
            case active
            case stages
            case boosters
            case costPerLaunch = "cost_per_launch"
            case successRate = "success_rate_pct"
            case firstFlight = "first_flight"
            case country
            case company
            case height
            case description
        }
    
    }

    *There are other options available (see, for example, Paw ), but this way is reasonably quick and easy.

    ** Pro tip: to expose the classes and types in your Sources folder to the main Playgrounds file, you have to explicitly mark their declarations as ‘public’.

    Explanation

    Our data will be contained in a struct called ‘RocketModel’. To use Codable, we mark our data types as ‘Codable’ when we declare them. Codable allows us to map our struct’s properties as one-to-one representations of our JSON object’s items by simply listing them out and assigning the appropriate types. For example, to include the JSON entries ‘id’, ‘name’, ‘type’, etc, we simply declare corresponding constants in our RocketModel struct:

    public struct RocketModel: Codable {
    public let id: String
          public let name: String
      public let type: String
      public let active: Bool
    }

    If the JSON object uses key names that differ from the property names we want to use, we can tell Codable to map those key names to our preferred names by declaring an enum with String and CodingKey as its associated types. Note that each case of the enum should match the property name that we declared in our list of properties.

    enum CodingKeys: String, CodingKey {
            case id
            case name
            case type
            case active
            case stages
            case boosters
            case costPerLaunch = "cost_per_launch"
            case successRate = "success_rate_pct"
            case firstFlight = "first_flight"
            case country
            case company
            case height
            case description
        }

    For those properties that require names that differ from those in the JSON object, list their JSON name as an associated string value. For example:

    case costPerLaunch = "cost_per_launch"

    Here’s a list of more detailed steps to map JSON objects to Swift value types:

  • 1. For JSON payloads that are unnamed arrays, decide on a name for each item in the array. For example, Rocket or RocketModel

  • 2. Create a typealias that is the plural of the name and declare its value as an array of the type you decided on in Step 1. For example:

  • typealias Rockets = [RocketModel]
  • 3. Next, list out as constants (with the appropriate types) all the dictionary entries in the JSON array in which you’re interested. Note, that if you’re only interested in a handful of entries, you can just include those; Codable will take care of the rest.

  • 4. For entries with nested entries or containers, make a separate struct, following the pattern used above (excluding the typealias) and assign the struct as the type of one of the items in the parent container. For example, for the ‘height’ entry—

  • "height": {
            "meters": 70,
            "feet": 229.6
     }

    —we’ll declare a 'Height' struct than conforms to Codable and has two properties, namely “meters” and “feet”:

    public struct Height: Codable {
        public let meters: Int
        public let feet: Double
    }
  • 5. Rinse and repeat until you have stubs for all the data in which you’re interested.

  • 6. Assign the payload to a data object, e.g. data and instantiate a decoder:

  • guard let url = Bundle.main.url(forResource: "Rockets", withExtension: "json") else {
        fatalError() // Don’t use fatalError in production code
    }
    guard let data = try? Data(contentsOf: url) else {
        fatalError()
    }
    
    let decoder = JSONDecoder()
  • 7. Then, decode the data object like this (making sure to handle any errors thrown during the decoding process):

  • do {
        let response = try decoder.decode(Rockets.self, from: data)
    
    for rocketModel in response {
       print(rocketModel.name)
    }
    } catch {
        print(error)
    }

    Notice that we’re passing in Rockets.self (the typealias that we declared in RocketModel.swift) as the model in which to place the data.

  • 8. To access a data property in the model, loop through the array of models in the response and use dot notation:

  • for rocketModel in response {
       print(rocketModel.name)
    }

    So . . . You Mentioned 5 Steps?

    Although this approach is significantly easier and more convenient than JSONSerialize, it still requires a fair bit of heavy lifting, a phrase which is synonymous to ‘qualifies for automation’. Moreover, at the top of this post I promised to demonstrate generating a data model in 5 easy steps. As it happens, a web app called QuickType solves this problem with only a modicum of effort on the developer’s side.

    Steps:

  • 2. From the ‘Language’ tab in panel on the right, select ‘Swift’ from the drop-down menu.

  • 3. Enter a name for your data model in the textbox on the left (‘Rockets’ in our case).

  • 4. Paste your JSON payload below the model name.

  • 5. Watch as QuickType generates your model for you.

  • Parsing a JSON payload and mapping its entries to a data model in Swift is far less of a hassle than it used to be now that Codable has entered the frame. Adding QuickType to your workflow allows you to quickly generate a template for a data model that you can finetune and prune as necessary.

    Available for Download: Xcode Playgrounds file

Pierre Liebenberg, Phase 2 UI/UX Designer and iOS Developer
Pierre Liebenberg Author
I’m a UI / UX Designer and sometime iOS Developer at Phase 2, a team of skilled, passionate, and engaged people who love crafting beautiful, usable software. You can see my most recent development work at lexico.app.