Saving Data in iOS

May 31 2022 · Swift 5.5, iOS 15, Xcode 13

Part 2: JSON

11. JSON Decoding

Episode complete

Play next episode

Next
About this episode

Leave a rating/review

See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 10. Introduction Next episode: 12. Challenge: Decoding JSON Arrays

Get immediate access to this and 4,000+ other videos and books.

Take your career further with a Kodeco Personal Plan. With unlimited access to over 40+ books and 4,000+ professional videos in a single subscription, it's simply the best investment you can make in your development career.

Learn more Already a subscriber? Sign in.

Notes: 11. JSON Decoding

Apple’s documentation on Encoding and Decoding Custom Types

This course was originally recorded in April 2020. It has been reviewed and all content and materials updated as of November 2021.

Transcript: 11. JSON Decoding

This video is where you’ll learn about the JSON file format. Or, as some other people pronounce it, “Jason”. Regardless of how you say it, JSON is currently a very popular format for transmitting data over the web. Working with JSON supplied by web services is a frequent task in app development.

Additionally JSON is also a viable solution for general-purpose data saving in iOS. The term “JSON” is used somewhat confusingly both for the data format, and colloquially as the data you’ll store in that format. For example: I just downloaded some fabulous JSON!

JSON is an acronym for JavaScript Object Notation. That’s where the “JS” acronym, often how JavaScript itself is abbreviated, comes from.

What JavaScript calls an “object” is the same concept as a heterogeneous Swift dictionary with strings for keys. “Heterogeneous” just means that the values can be of more than one type, of multiple types.

Another term for this kind of data structure is “associative array“, but neither Swift nor JavaScript use these terms. In Swift, you use the same angle bracket syntax for arrays and dictionaries. JavaScript arrays also use angle brackets.

But even though JS objects are more like dictionaries than Swift classes or structures, their syntax involves being wrapped up in curly braces. Aside from that, their syntax is just like Swift dictionary literals.

JSON supports only a few data types natively. Your basic booleans, numbers, and strings are all available, and as long as they hold supported types, so are Arrays.

The same goes for dictionaries with string keys. Data is not directly supported. Instead, you can store Data bytes in encoded Strings and dates in properly-formatted strings.

It’s demo time! From now on, you will be working with a proper Xcode project that builds an iOS app of Tasks, or Reminders.

To familiarize yourself with the app, open the TaskList Xcode project and run it in an iPhone simulator of your choice.

Note how you have a list of tasks already created for you, each within different categories of priority. You can swipe to delete or enter edit mode, or tap the ‘+’ button to create new tasks. Add one or two tasks, delete some as well, then stop the app and run it again.

What happened? Well, as of now the app is using hard-coded data, which means that none of your changes are being persisted (or saved) between app launches.

Now it’s time to look at how the app is structured. This is a regular SwiftUI app, but of interest to you are the items in the Models folder and some of the items in the Views folder.

One of the model objects is the Task Swift Structure that adheres to the identifiable protocol (to work correctly and seamlessly with SwiftUI), and defines some properties.

You also have an extension of Task for an enumeration called Priority. This is how you can categorize and prioritize your tasks.

Next up there’s the PrioritizedTasks Structure that contains an array of tasks along with a priority for them. The extension in this file is so there is a unique identifier for a PrioritizedTask.

Finally, you have a TaskStore Swift Structure that stores and manages your tasks. This is how the app will interface with your data. The store contains a property for an array of prioritized tasks, and a helper method to get the index in the prioritizedTasks array that matches the priority passed to the method as a parameter.

There is also a private extension for another model object, the PrioritizedTasks Structure to help with its initialization. Moving on to some of the views, let’s focus on ContentView for now.

This is a SwiftUI View that, in its body, creates a NavigationView with a List of tasks. The list is separated into sections, each one corresponding to a priority, and then the section contents have the actual tasks for that priority.

The body of this View also adds the ‘Edit’ and ‘Add’ buttons with the appropriate interactions. One final thing I want to draw your attention to are the two JSON files in the Project Navigator; Task.json, and PrioritizedTask.json. Each file corresponds to a single object of each type represented in JSON.

The structure looks very similar to Swift dictionaries, but instead of square brackets you use curly braces. Task.json is a dictionary, representing a single Task object, with key-value pairs for id, name, and completed.

PrioritizedTask.json is very similar, with the interesting thing being the tasks key with that corresponds to a JSON array of tasks.

That was, at a high level, an overview of the app, the model objects, some views, and what JSON files look like. In the next few videos you’ll go from loading data from a file in your app bundle, to loading and saving your data from your app’s Document directory.

One step at a time, though, for now you’ll practice decoding those JSON files into native Swift types and ensuring everything gets correctly set up in your model. Open ContentView.swift and add a private method called loadJSON.

// MARK: - Private Methods
private func loadJSON() {
}

The first thing you want to do in this method is to get the URLs for each of the JSON files. For that you’ll use Bundle’s url(forResource:withExtension:) method.

guard let taskJSONURL = Bundle.main.url(forResource: <#T##String?#>, withExtension: <#T##String?#>)

Bundle is a representation of your app, its code, and resources. All the files you add in Xcode to your app target are packaged up into what is referred to as the app bundle. main represents the current executable, in this case your app. So by asking your main bundle for the URL for a given resource, and assuming it’s located in the bundle, you’ll get back the URL where the file is located.

The forResource parameter is the name of the file you want the URL for.

"Task"

withExtension is the file extension of the file.

"json"

Add another let to get the URL for the PrioritizedTask.json file.

guard let taskJSONURL = Bundle.main.url(forResource: "Task", withExtension: "json"),
  let prioritizedTaskJSONURL = Bundle.main.url(forResource: "PrioritizedTask", withExtension: "json") else {
    return
}

You do this in a guard statement as there isn’t much you can do without these URLs. After the guard statement, if you are able to get the URLs, it’s time to create a JSONDecoder object.

let decoder = JSONDecoder()

JSONDecoder helps you decode JSON objects into instances of a given data type, in your case it will be instances of Task and PrioritizedTask.

The steps to achieve this are to load the contents of your JSON files as Swift Data, and then decode that Data into your types.

let taskData = Data(contentsOf: taskJSONURL)

You use the Data initializer that takes a URL, it can throw an exception, so you wanna mark the call with a try and also wrap this code in a do-catch statement.

do {
    let taskData = try Data(contentsOf: taskJSONURL)
  } catch let error {
    print(error)
}

If there is an error then you print that to the console for debugging purposes. Do the same for the prioritized tasks file.

let prioritizedTaskData = try Data(contentsOf: prioritizedTaskJSONURL)

If everything is correct so far you should not see any errors in Xcode. This also means you have two Data objects that need to be decoded, from JSON data, to a Swift type. Start with the task data by using the decode method.

let task = try decoder.decode(<#T##type: Decodable.Protocol##Decodable.Protocol#>, from: <#T##Data#>)
print(task)

The first parameter is the type to decode from your data. You are loading a Task, so using Task.self will tell that to the method.

let task = decoder.decode(Tesk.self, from: <#T##Data#>)

The from parameter is the JSON object to decode.

let task = decoder.decode(Task.self, from: taskData)

This method also throws an exception, so be sure to mark it with try accordingly.

let task = try decoder.decode(Task.self, from: taskData)

Everything should be fine, right? So why is Xcode saying that Task doesn’t comform to Codable? Well, even though the JSON file exactly matches a Task object, you need to have your Task type conform to the Codable protocol for it to be able to convert to and from a different representation.

Because you are using JSONDecoder and its decode method, the representation is JSON.

Make your Task Structure conform to Codable.

struct Task: Identifiable, Codable {
	...
}

By default, you don’t need to implement any methods for your types to conform to Codable. As long as all the types used within a Swift type are also Codable, then you get a ton of functionality out-of-the-box.

And note that, in ContentView, there is no longer an error when trying to decode your data. Great work! To be able to view what got loaded, go ahead and print your task object.

print(task)

Right now there is no place that calls this method to load your JSON data. Fix that by adding the onAppear method to your ContentView’s body.

onAppear {
  self.loadJSON()
}

And build and run your app.

Awesome! If you look at the Task.json file and compare it to the output in the Xcode Console, you will see that they are identical.

I’m already getting excited with the possibilities that this will open up. No more forgotten wallet or sunglasses, yay! In your loadJSON method, do the same to load the PrioritizedTask data.

let prioritizedTask = try decoder.decode(TaskStore.PrioritizedTasks.self, from: prioritizedTaskData)
print(prioritizedTask)

An identical process except that you decode the priotizedTaskDatainto an object of type PrioritizedTask. Don’t forget to make PrioritizedTask conform to the Codable protocol so you can decode and encode it (remember, we’re using JSON for now).

struct PrioritizedTasks, Codable {
  ...
}

Why is Xcode showing a warning now? PrioritizedTaskis now conforming to the Codable protocol, isn’t it? Well, not quite. Priority isn’t a native Swift type and, thus, it too needs too needs to comform to Codable.

Build and run your app one last time, no errors yay! And check out the output in the console.

It’s not the prettiest looking console output, but you can see that the PrioritizedTask directly matches the JSON data in your bundle’s file. Great work!