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