Make App Pie

Training for Developers and Artists

Quick JSON Decoding

iOS Development tips weekly is a series you can find at the Lynda.com and LinkedIn Learning libraries. The first week of a week’s tip will be available to the public. After that, you will need a subscription to get access to it. Click the image the left  to view.  Usually you’ll find a transcript below. This lesson will not have a transcript but an extended explanation of the topic.

A very popular data file is JSON. Apple uses it in Xcode and for remote notifications. As developers we may need to read JSON files into our code and translate them into usable objects in Swift. With the JSON decoder, it’s easier than pronouncing JSON(jay-son), or is it JSON(jays-on).
I’ll do this is a clean playground file, so open one up if you wish to follow along.  I’ll also make sure the Run on the playground is set to Manual by holding down the play button and selecting manual. To save us time messing with file loading, I’m going to make a literal for the Json file:

var myJSON = """ """

I’ll use the three Quote string delimiters to allow for a multiline string. this is supposed to be of type Data, which I can set up with the data property

var myJSON = """ 

""".data(using: .utf8)

JSON is a collection of objects, with a key and a value, much like a dictionary in Swift. For example I’ll add this:

{
"name":"Huli Pizza Company"
}

The braces encase the object, the first string is the key, and we have a value as a string of a restaurant name. I’ll add two more another string and a integer, separating each with a comma.

{
"name":"Huli Pizza Company",
"cuisine":"Pizza",
"rating": 5
}

JSON take most basic data types like strings, Integers, Floats, Bools, and Doubles.
While this looks a lot like dictionary, I’ll represent it in Swift as a struct

struct Restaurant{
    var name: String
    var cuisine: String
    var rating: Int 
}

By adding a Codable protocol, I make this able to code and decode a JSON file.

struct Restaurant : Codable {
    var name: String
    var cuisine: String
    var rating: Int 
}

The Codable protocol will do much of  heavy lifting in converting our JSON data to a Swift Object. We’ll need one more object for a JSON decoder.

let decoder = JSONDecoder()

I use the decoder to decode the JSON file into the Restaurant struct. decoder throws an error so I’ll add a try? to this.

let restaurant  = try? decoder.decode(Restaurant.self, from: myJSON!)

That’s it. You have the JSON file converted to the restaurant Struct.  If it didn’t convert, restaurant will be nil due to the try?. Let’s look at that data by unwrapping the optional and print the name and rating of the restaurant.

if let restaurant = restaurant {
    print(restaurant.name)
    print("Rating: \(restaurant.rating) Slices")
}

I’ll also add for a nil case, when there is an error decoding the JSON file.

} else {
    print("Not able to decode JSON")
}

Run this and the console shows the name and rating of the restaurant.

Huli Pizza Company
Rating: 5 Slices

Add JSON Arrays

JSON can have arrays as a type. You can add that like this using Square brackets.

"locations":["Hilo","Honolulu","Waimea","Lihue"]

Remember to add a comma after the rating.

Add to the struct the array like this:

var locations:[String]

Print out the locations as a loop:

for location in restaurant.locations{
        print(location)
    }

Run this and you get the locations.

Huli Pizza Company
Rating: 5 Slices
Hilo
Honolulu
Waimea
Lihue 

Nested Objects in JSON

The real power of JSON is making objects within objects. I might have a menu Item object I use for a menu item and a price. for example:

"bestMenuItem":{"item":"Huli Chicken Pizza","price":14.95}

I’ll make a new struct for this, again adopting Codable

struct MenuItem : Codable {
    var item:String
    var price:Double
}

Then add the MenItem type to the Restaurant type.

var bestMenuItem:MenuItem

Print this in our print block

print("The best Menu Item is \(restaurant.bestMenuItem.item)")

Run again and you get the best menu item

The best Menu Item is Huli Chicken Pizza

Objects in an Array

Commonly you can add an object multiple times in an array. I’ll do some cutting and pasting here to make this array of menu items.

"menuItems": [
   {"item":"Huli Chicken Pizza","price":14.95},
   {"item":"Coconut Gelato","price":5.95},
   {"item":"Kona Coffee","price":4.95}
]

Add the menu items as an array of menu items

var menuItems:[MenuItem]

Print them out.

 
for item in restaurant.menuItems{
        print ("\(item.item) $\(item.price)")
    }

Run this and you’ll get:

Huli Chicken Pizza $14.95
Coconut Gelato $5.95
Kona Coffee $4.95

Differently Named Keys

There’s one last thing I want to look at. When reading other people’s files they might not use the same nomenclature you do. Right now all of our indentifiers in the Restaurant Struct are the same as the JSON keys. What if bestMenuItem had underscores like this?

"best_Menu_Item":{"item":"Huli Chicken Pizza","price":14.95},

Run and you’ll see the JSON does not decode. We’ve been using default mode for Coding. There’s an enum with the indentifier CodingKeys which controls the keys you will read and the name of the key. Add this to the Restaurant struct:

private enum CodingKeys : String, CodingKey {
}

The cases within it are the keys we’ll read from the JSON File, matching the identifiers in the Restaurant struct. If they don’t match you’ll get the error

Type 'Restaurant' does not conform to protocol 'Decodable'

You must list the keys you read in the enum, so add these:

 
       case name
        case rating
        case locations
        case bestMenuItem
        case menuItems

If I don’t want to read a key, I leave it out of this enum and don’t define a variable in the struct. I left off Cuisine, and comment it out of the struct for example.

 //case cuisine
 

The error disappears.

For the key name that’s different, I set the value for the case.

case bestMenuItem = "best_Menu_Item"

Run this, and the JSON loads without problems.

Rating: 5 Slices
Hilo
Honolulu
Waimea
Lihue
The best Menu Item is Huli Chicken Pizza
Huli Chicken Pizza $14.95
Coconut Gelato $5.95
Kona Coffee $4.95

You can of course write JSON files as well using the JSONEncoder class and the instance method encode from the defined class. I’ll let you play around with those, its the reverse process from reading. The iOS Development Weekly Tips on this goes into writing more. You can also check out the final code for that session on GitHub. You can also validate the data coming in through as well. I’ll be covering that in later tips and tricks. If you can’t wait, check out the WWDC 2017 video What’s new in Foundation at about the 23 minute point.

The Whole Code

Here’s the code from the playground used above. You can also download it from GitHub.. For another example with reading and writing, check this out:

//: Read a JSON file using the Codable protocol 

import UIKit

var myJSON = """
{
    "name":"Huli Pizza Company",
    "cuisine":"Pizza",
    "rating": 5,
    "locations":["Hilo","Honolulu","Waimea","Lihue"],
    "best_Menu_Item":{"item":"Huli Chicken Pizza","price":14.95},
    "menuItems": [
        {"item":"Huli Chicken Pizza","price":14.95},
        {"item":"Coconut Gelato","price":5.95},
        {"item":"Kona Coffee","price":4.95}
    ]
}
""".data(using: .utf8)

struct MenuItem : Codable {
    var item:String
    var price:Double
}

struct Restaurant : Codable {
    private enum CodingKeys : String, CodingKey {
        case name
        case rating
        case locations
        case bestMenuItem = "best_Menu_Item"
        case menuItems
    }
    var name: String
    //var cuisine: String
    var rating: Int
    var locations:[String]
    var bestMenuItem:MenuItem
    var menuItems:[MenuItem]
}

let decoder = JSONDecoder()
let restaurant  = try? decoder.decode(Restaurant.self, from: myJSON!)

if let restaurant = restaurant {
    print(restaurant.name)
    print("Rating: \(restaurant.rating) Slices")
    for location in restaurant.locations{
        print(location)
        
    }
    print("The best Menu Item is \(restaurant.bestMenuItem.item)")
    for item in restaurant.menuItems{
        print ("\(item.item) $\(item.price)")
    }
} else {
    print("Not able to decode JSON")
}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: