Chapter 4
Working with JSON and Codable in Swift

First, what's JSON? JSON (short for JavaScript Object Notation) is a text-based, lightweight, and easy way for storing and exchanging data. It's commonly used for representing structural data and data interchange in client-server applications, serving as an alternative to XML. A lot of the web services we use every day have JSON-based APIs. Most of the iOS apps, including Twitter, Facebook, and Flickr send data to their backend web services in JSON format.

As an example, here is a JSON representation of a sample Movie object:

{
    "title": "The Amazing Spider-man",
    "release_date": "03/07/2012",
    "director": "Marc Webb",
    "cast": [
        {
            "name": "Andrew Garfield",
            "character": "Peter Parker"
        },
        {
            "name": "Emma Stone",
            "character": "Gwen Stacy"
        },
        {
            "name": "Rhys Ifans",
            "character": "Dr. Curt Connors"
        }
    ]
}

As you can see, JSON formatted data is more human-readable and easier to parse than XML. I'll not go into the details of JSON. This is not the purpose of this chapter. If you want to learn more about the technology, I recommend you to check out the JSON Guide at http://www.json.org/.

Since the release of iOS 5, the iOS SDK has already made it easy for developers to fetch and parse JSON data. It comes with a handy class called NSJSONSerialization, which can automatically convert JSON formatted data to objects. Later in this chapter, I will show you how to use the API to parse some sample JSON formatted data, returned by a web service. Once you understand how it works, it is fairly easy to build an app by integrating with other free/paid web services.

Since the release of Swift 4, Apple introduced the Codable protocol to simplify the whole JSON archival and serialization process. We will also look into this new feature and see how we can apply it in JSON parsing.

Demo App

As usual, we'll create a demo app. Let's call it KivaLoan. The reason why we name the app KivaLoan is that we will utilize a JSON-based API provided by Kiva.org. If you haven't heard of Kiva, it is a non-profit organization with a mission to connect people through lending to alleviate poverty. It lets individuals lend as little as $25 to help create opportunities around the world. Kiva provides free web-based APIs for developers to access their data. For our demo app, we'll call up the following Kiva API to retrieve the most recent fundraising loans and display them in a table view:

https://api.kivaws.org/v1/loans/newest.json
Quick note: Starting from iOS 9, Apple introduced a feature called App Transport Security (ATS) with the aim to improve the security of connections between an app and web services. By default, all outgoing connections should ride on HTTPS. Otherwise, your app will not be allowed to connect to the web service. Optionally, you can add a key named NSAllowsArbitraryLoads in the Info.plist and set the value to YES to disable ATS, so that you can connect to web APIs over HTTP. 

However, you'll have to take note if you use NSAllowsArbitraryLoads in your apps. In iOS 10, Apple further enforces ATS for all iOS apps. By January 2017, all iOS apps should be ATS-compliant. In other words, if your app connects to external any web services, the connection must be over HTTPS. If your app can't fulfil this requirement, Apple will not allow it to be released on the App Store.

The returned data of the above API is in JSON format. Here is a sample result:

loans: (
        {
        activity = Retail;
        "basket_amount" = 0;
        "bonus_credit_eligibility" = 0;
        "borrower_count" = 1;
        description =         {
            languages =             (
                fr,
                en
            );
        };
        "funded_amount" = 0;
        id = 734117;
        image =         {
            id = 1641389;
            "template_id" = 1;
        };
        "lender_count" = 0;
        "loan_amount" = 750;
        location =         {
            country = Senegal;
            "country_code" = SN;
            geo =             {
                level = country;
                pairs = "14 -14";
                type = point;
            };
        };
        name = "Mar\U00e8me";
        "partner_id" = 108;
        "planned_expiration_date" = "2016-08-05T09:20:02Z";
        "posted_date" = "2016-07-06T09:20:02Z";
        sector = Retail;
        status = fundraising;
        use = "to buy fabric to resell";
    },
....
....
)

You will learn how to use the NSJSONSerialization class to convert the JSON formatted data into objects. It's unbelievably simple. You'll see what I mean in a while.

To keep you focused on learning the JSON implementation, you can first download the project template from http://www.appcoda.com/resources/swift59/KivaLoanStarter.zip. I have already created the skeleton of the app for you. It is a simple table-based app that displays a list of loans provided by Kiva.org. The project template includes a pre-built storyboard and custom classes for the table view controller and prototype cell. If you run the template, it should result in an empty table app.

Figure 4.1. Kiva Loan Project Template - Storyboard
Figure 4.1. Kiva Loan Project Template - Storyboard

Creating JSON Data Model

We will first create a class to model a loan. It's not required for loading JSON but the best practice is to create a separate class (or structure) for storing the data model. The Loan class represents the loan information in the KivaLoan app and is used to store the loan information returned by Kiva.org. To keep things simple, we won't use all the returned data of a loan. Instead, the app will just display the following fields of a loan:

  • Name of the loan applicant
name = "Mar\U00e8me";
  • Country of the loan applicant
location =         {
            country = Senegal;
            "country_code" = SN;
            geo =             {
                level = country;
                pairs = "14 -14";
                type = point;
            };
        };
  • How the loan will be used
use = "to buy fabric to resell";
  • Amount
"loan_amount" = 750;

These fields are good enough for filling up the labels in the table view. Now create a new class file using the Swift File template. Name it Loan.swift and declare the Loan structure like this:

struct Loan: Hashable {

    var name: String = ""
    var country: String = ""
    var use: String = ""
    var amount: Int = 0

}

JSON supports a few basic data types including number, String, Boolean, Array, and Objects (an associated array with key and value pairs).

For the loan fields, the loan amount is stored as a numeric value in the JSON-formatted data. This is why we declared the amount property with the type Int. For the rest of the fields, they are declared with the type String.

Fetching Loans with the Kiva API

As I mentioned earlier, the Kiva API is free to use. No registration is required. You may point your browser to the following URL and you'll get the latest fundraising loans in JSON format.

https://api.kivaws.org/v1/loans/newest.json

Okay, let's see how we can call up the Kiva API and parse the returned data. First, open KivaLoanTableViewController.swift and declare two variables at the very beginning:

private let kivaLoanURL = "https://api.kivaws.org/v1/loans/newest.json"
private var loans = [Loan]()

We just defined the URL of the Kiva API, and declare the loans variable for storing an array of Loan objects. Next, insert the following methods in the same file:

func getLatestLoans() {
    guard let loanUrl = URL(string: kivaLoanURL) else {
        return
    }

    let request = URLRequest(url: loanUrl)
    let task = URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) -> Void in

        if let error = error {
            print(error)
            return
        }

        // Parse JSON data
        if let data = data {
            self.loans = self.parseJsonData(data: data)

            // Update table view's data
            OperationQueue.main.addOperation({ 
                self.updateSnapshot()
            })
        }
    })

    task.resume()
}

func parseJsonData(data: Data) -> [Loan] {

    var loans = [Loan]()

    do {
        let jsonResult = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as? NSDictionary

        // Parse JSON data
        let jsonLoans = jsonResult?["loans"] as! [AnyObject]
        for jsonLoan in jsonLoans {
            var loan = Loan()
            loan.name = jsonLoan["name"] as! String
            loan.amount = jsonLoan["loan_amount"] as! Int
            loan.use = jsonLoan["use"] as! String
            let location = jsonLoan["location"] as! [String:AnyObject]
            loan.country = location["country"] as! String
            loans.append(loan)
        }

    } catch {
        print(error)
    }

    return loans
}

These two methods form the core part of the app. Both methods work collaboratively to call the Kiva API, retrieve the latest loans in JSON format and translate the JSON-formatted data into an array of Loan objects. Let's go through them in detail.

In the getLatestLoans method, we first instantiate the URL structure with the URL of the Kiva Loan API. The initialization returns us an optional. This is why we use the guard keyword to see if the optional has a value. If not, we simply return and skip all the code in the method.

Next, we create a URLSession with the load URL. The URLSession class provides APIs for dealing with online content over HTTP and HTTPS. The shared session is good enough for making simple HTTP/HTTPS requests. In case you have to support your own networking protocol, URLSession also provides you an option to create a custom session.

One great thing of URLSession is that you can add a series of session tasks to handle the loading of data, as well as uploading and downloading files and data fetching from servers (e.g. JSON data fetching).

With sessions, you can schedule three types of tasks: data tasks (URLSessionDataTask) for retrieving data to memory, download tasks (URLSessionDownloadTask) for downloading a file to disk, and upload tasks (URLSessionUploadTask) for uploading a file from disk. Here we use the data task to retrieve contents from Kiva.org. To add a data task to the session, we call the dataTask method with the specific URL request. After you add the task, the session will not take any action. You have to call the resume method (i.e. task.resume()) to initiate the data task.

Like most networking APIs, the URLSession API is asynchronous. Once the request completes, it returns the data (as well as errors) by calling the completion handler.

In the completion handler, immediately after the data is returned, we check for an error. If no error is found, we invoke the parseJsonData method.

The data returned is in JSON format. We create a helper method called parseJsonData for converting the given JSON-formatted data into an array of Loan objects. The Foundation framework provides the JSONSerialization class, which is capable of converting JSON to Foundation objects and converting Foundation objects to JSON. In the code snippet, we call the jsonObject method with the given JSON data to perform the conversion.

When converting JSON formatted data to objects, the top-level item is usually converted to a Dictionary or an Array. In this case, the top level of the returned data of the Kiva API is converted to a dictionary. You can access the array of loans using the key loans.

How do you know what key to use?

You can either refer to the API documentation or test the JSON data using a JSON browser (e.g. http://jsonviewer.stack.hu). If you've loaded the Kiva API into the JSON browser, here is an excerpt from the result:

{
  "paging": {
    "page": 1,
    "total": 5297,
    "page_size": 20,
    "pages": 265
  },
  "loans": [
    {
      "id": 794429,
      "name": "Joel",
      "description": {
        "languages": [
          "es",
          "en"
        ]
      },
      "status": "fundraising",
      "funded_amount": 0,
      "basket_amount": 0,
      "image": {
        "id": 1729143,
        "template_id": 1
      },
      "activity": "Home Appliances",
      "sector": "Personal Use",
      "use": "To buy home appliances.",
      "location": {
        "country_code": "PE",
        "country": "Peru",
        "town": "Ica",
        "geo": {
          "level": "country",
          "pairs": "-10 -76",
          "type": "point"
        }
      },
      "partner_id": 139,
      "posted_date": "2015-11-20T08:50:02Z",
      "planned_expiration_date": "2016-01-04T08:50:02Z",
      "loan_amount": 400,
      "borrower_count": 1,
      "lender_count": 0,
      "bonus_credit_eligibility": true,
      "tags": [

      ]
    },
    {
      "id": 797222,
      "name": "Lucy",
      "description": {
        "languages": [
          "en"
        ]
      },
      "status": "fundraising",
      "funded_amount": 0,
      "basket_amount": 0,
      "image": {
        "id": 1732818,
        "template_id": 1
      },
      "activity": "Farm Supplies",
      "sector": "Agriculture",
      "use": "To purchase a biogas system for clean cooking",
      "location": {
        "country_code": "KE",
        "country": "Kenya",
        "town": "Gatitu",
        "geo": {
          "level": "country",
          "pairs": "1 38",
          "type": "point"
        }
      },
      "partner_id": 436,
      "posted_date": "2016-11-20T08:50:02Z",
      "planned_expiration_date": "2016-01-04T08:50:02Z",
      "loan_amount": 800,
      "borrower_count": 1,
      "lender_count": 0,
      "bonus_credit_eligibility": false,
      "tags": [

      ]
    },

     ...

As you can see from the above code, paging and loans are two of the top-level items. Once the JSONSerialization class converts the JSON data, the result (i.e. jsonResult) is returned as a Dictionary with the top-level items as keys. This is why we can use the key loans to access the array of loans. Here is the line of code for your reference:

let jsonLoans = jsonResult?["loans"] as! [AnyObject]

With the array of loans (i.e. jsonLoans) returned, we loop through the array. Each of the array items (i.e. jsonLoan) is converted into a dictionary. In the loop, we extract the loan data from each of the dictionaries and save them in a Loan object. Again, you can find the keys (highlighted in yellow) by studying the JSON result. The value of a particular result is stored as AnyObject. AnyObject is used because a JSON value could be a String, Double, Boolean, Array, Dictionary or null. This is why you have to downcast the value to a specific type such as String and Int. Lastly, we put the loan object into the loans array, which is the return value of the method.

for jsonLoan in jsonLoans {
    var loan = Loan()

    loan.name = jsonLoan["name"] as! String
    loan.amount = jsonLoan["loan_amount"] as! Int
    loan.use = jsonLoan["use"] as! String
    let location = jsonLoan["location"] as! [String: AnyObject]
    loan.country = location["country"] as! String

    loans.append(loan)
}

After the JSON data is parsed and the array of loans is returned, we call the reloadData method to reload the table. You may wonder why we need to call OperationQueue.main.addOperation and execute the data reload in the main thread.

The block of code in the completion handler of the data task is executed in a background thread. If you call the updateSnapshot method in the background thread, the data reload will not happen immediately. To ensure a responsive GUI update, this operation should be performed in the main thread. This is why we call the OperationQueue.main.addOperation method and request to run the updateSnapshot method in the main queue.

OperationQueue.main.addOperation({ 
    self.updateSnapshot()
})
Quick note: You can also use dispatch_async function to execute a block of code in the main thread. But according to Apple, it is recommended to use OperationQueue over dispatch_async. As a general rule, Apple recommends using the highest-level APIs rather than dropping down to the low-level ones.

We haven't implemented the updateSnapshot() method yet, which will be discussed in the next section.

Displaying Loans in A Table View

With the loans array in place, the last thing we need to do is to display the data in the table view. First, declare a Section enum in KivaLoanTableViewController:

enum Section {
    case all
}

Next, insert the following methods in the same class:

func configureDataSource() -> UITableViewDiffableDataSource<Section, Loan> {

    let cellIdentifier = "Cell"

    let dataSource = UITableViewDiffableDataSource<Section, Loan>(
        tableView: tableView,
        cellProvider: {  tableView, indexPath, loan in
            let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! KivaLoanTableViewCell

            cell.nameLabel.text = loan.name
            cell.countryLabel.text = loan.country
            cell.useLabel.text = loan.use
            cell.amountLabel.text = "$\(loan.amount)"

            return cell
        }
    )

    return dataSource
}

func updateSnapshot(animatingChange: Bool = false) {

    // Create a snapshot and populate the data
    var snapshot = NSDiffableDataSourceSnapshot<Section, Loan>()
    snapshot.appendSections([.all])
    snapshot.appendItems(loans, toSection: .all)

    dataSource.apply(snapshot, animatingDifferences: animatingChange)
}

The above code is pretty straightforward if you understand how to implement table view using UITableViewDiffableDataSource, which is now the recommended approach to populate a table view's data. If you are new to UITableViewDiffableDataSource, please refer to our beginner book.

In brief, the configureDataSource method is written to retrieve the loan information from the loans array and populate them in the custom table cell. One thing to take note of is the code below:

"$\(loan.amount)"

In some cases, you may want to create a string by adding both string (e.g. $) and integer (e.g. "$\(loan.amount)") together. Swift provides a powerful way to create these kinds of strings, known as string interpolation. You can make use of it by using the above syntax.

The updateSnapshot method is designed to populate the loans into the table.

Next, declare a dataSource variable like this:

lazy var dataSource = configureDataSource()

Lastly, insert the following line of code in the viewDidLoad method to start fetching the loan data:

getLatestLoans()

Compile and Run the App

Now it's time to test the app. Compile and run it in the simulator. Once launched, the app will pull the latest loans from Kiva.org and display them in the table view.

Figure 4.2. The demo app retrieves and displays the latest loan from kiva.org
Figure 4.2. The demo app retrieves and displays the latest loan from kiva.org

Introducing Codable

Starting from Swift 4, Apple introduced a new way to encode and decode JSON data using Codable. We will rewrite the JSON decoding part of the demo app using this new approach.

Before we jump right into the modification, let me give you a basic walkthrough of Codable. If you look into the documentation of Codable, it is just a type alias of a protocol composition:

typealias Codable = Decodable & Encodable

Decodable and Encodable are the two actual protocols you need to work with. However, for convenience's sake, we usually refer to this type alias for handling JSON encoding and decoding.

To continue reading and access the full version of the book, please get the full copy here. You will also be able to access the full source code of the project.

results matching ""

    No results matching ""