Monday, June 13, 2016

Parsing complex JSON data in Swift 2!

This tutorial will show you how to parse a downloaded, somewhat complex, JSON object in Swift 2.2.

I have created 2 previous tutorials related to this, which get into more details on how to download and parse basic JSON data, and how to return it using completion handlers.
This tutorial is specifically to show you how to parse complex data, and traverse a JSON object tree.
This tutorial assumes you already know how to get the data downloaded, and understand how JSON parsing works.

Project setup

Since this project is all about parsing, I will not be making a UI or any other changes to a basic application.
This application starts from a brand new "Single view application" template of Xcode.

All the code created here will be used in the viewDidAppear() function of the default ViewController.swift class.

Sample JSON data

This tutorial will be using a JSON data I copied from an adobe JSON tutorial, and placed it on a new file so it is easier to download.

This is the url we'll be using: https://api.myjson.com/bins/x4l8
In case the url is down by the time you read this tutorial, here's the content of the entire file/url:
{

    "id": "0001",
    "type": "donut",
    "name": "Cake",
    "ppu": 0.55,
    "batters": {
        "batter": [
            {
                "id": "1001",
                "type": "Regular"
            },
            {
                "id": "1002",
                "type": "Chocolate"
            },
            {
                "id": "1003",
                "type": "Blueberry"
            },
            {
                "id": "1004",
                "type": "Devil's Food"
            }
        ]
    },
    "topping": [
        {
            "id": "5001",
            "type": "None"
        },
        {
            "id": "5002",
            "type": "Glazed"
        },
        {
            "id": "5005",
            "type": "Sugar"
        },
        {
            "id": "5007",
            "type": "Powdered Sugar"
        },
        {
            "id": "5006",
            "type": "Chocolate with Sprinkles"
        },
        {
            "id": "5003",
            "type": "Chocolate"
        },
        {
            "id": "5004",
            "type": "Maple"
        }
    ]

}

Download data

With all of the setup elements out of the way, we will turn to Xcode and start working on this.
Let's begin by downloading the data. Since this is the same code we used and explained in the first 2 tutorials, I will just copy the code block and assume you understand what I'm doing.
import UIKit
class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // this is sample data I copied from Adobe.github.io
        // adobe.github.io/Spry/samples/data_region/JSONDataSetSample.html#Example2
        
        let jsonUrlAsString = "https://api.myjson.com/bins/x4l8"
        
        // make the GET call asynchrounously
        let configuration = NSURLSessionConfiguration.defaultSessionConfiguration();
        let headers: [NSObject : AnyObject] = ["Accept":"application/json"];
        configuration.HTTPAdditionalHeaders = headers;
        let session = NSURLSession(configuration: configuration)
        let dataTask = session.dataTaskWithURL(NSURL(string: jsonUrlAsString)!) { (data, response, error) in
            
            if(error == nil)
            {
            }
            else
            {
                print("Error downloading JSON data. Error = \(error)")
            }
        }
        dataTask.resume()
    }
}
And with the data downloaded, we need to begin our parsing process.
But first...

Understand JSON

Note: if you already understand what JSONs are, you may want to skip this section.
JSON strings are super easy. They are based on two concepts: JSON Objects and JSON Arrays, and key value pairs.

Key value pairs

These are 2 words that are separated with a colon (:), and each key value pair is separated with a comma (,). They are presented as this:

"key1":"value1", "key2":"value2","myKey":"my value","monster":"Cookie Monster", "myAge":32

Keys can have spaces, but they usually have issues on some programming languages, so that makes most keys to always be without spaces.
Values can be whatever you want. If they're Strings then they have quotes ("") around them, while integers and doubles don't have them. However, sometimes the output from the log from an IDE might place quotes around everything.

JSON Objects and JSON Arrays

By default a single JSON string contains a single JSON Object, but the most important part to remember about JSON Objects and JSON Arrays is this:

JSON Object are represented as {} (curly braces)
JSON Arrays are (usually) represented as [] (square brackets)

Note: XCode usually outputs JSON Arrays as () (parenthesis)

So with that, we can create a JSON Object like this:
{
 "key1": "value1",
 "monster": "Cookie Monster"
}
This is a JSON Object with 2 key values (we call that fields now)
Before we talk about JSON Arrays, you need to see what we can do now: we can do a key value pair with a key a JSON Object, like this:
{
 "key1": "value1",
 "monster": "Cookie Monster",
 "carObject": {
  "brand": "Ford",
  "model": "Model A"
 },
 "computerObject": {
  "brand": "Apple",
  "model": "Macbook pro"
 }
}
In here, we not have a key of carObject with a value of a new JSON Object. This new JSON Object has it's own set of key values. Same thing with computerObject.

JSON Arrays are basically what you would expect them to be: an array of JSON Objects. The only thing to remember is that JSON Arrays wrap JSON Objects, so they are represented before a curly brace.
Here's an example of a JSON Object with a JSON Array:
{
 "key1": "value1",
 "monster": "Cookie Monster",
 "carObject": [{
  "brand": "Ford",
  "model": "Model A"
 }, {
  "brand": "Chevy",
  "model": "Camaro"
 }]
}
So now inside the key of carObject we now have a JSON Array of JSON Objects. In this case we have 2 JSON Objects, each of them with their own key value pairs.

Actual Parsing process

Just like in the previous tutorials, we first need to convert our downloaded data into a JSON object, and we will do this using NSJSONSerialization.JSONObjectWithData function from the Foundation framework.
Then, instead copying one line at the time, I'm gonna copy the whole thing I did, and tell you later what it means.
Here's my entire class code:
import UIKit
class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // this is sample data I copied from Adobe.github.io
        // adobe.github.io/Spry/samples/data_region/JSONDataSetSample.html#Example2
        
        let jsonUrlAsString = "https://api.myjson.com/bins/x4l8"
        
        // 1. make the GET call asynchrounously
        let configuration = NSURLSessionConfiguration.defaultSessionConfiguration();
        let headers: [NSObject : AnyObject] = ["Accept":"application/json"];
        configuration.HTTPAdditionalHeaders = headers;
        let session = NSURLSession(configuration: configuration)
        let dataTask = session.dataTaskWithURL(NSURL(string: jsonUrlAsString)!) { (data, response, error) in
            
            if(error == nil)
            {
                do
                {
                    // 2. convert downloaded NSData into JSONObject
                    let entireJson = try NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments);
                    print("entireJson = \(entireJson)")
                    
                    // 3. parse root elements
                    let rootId = entireJson["id"] as? String
                    let rootType = entireJson["type"] as? String
                    let rootName = entireJson["name"] as? String
                    let rootPpu = entireJson["ppu"] as? Double          // no idea what ppu is or means
                   
                    //output result to console
                    print("rootId = \(rootId!)")
                    print("rootType = \(rootType!)")
                    print("rootName = \(rootName!)")
                    print("rootPpu = \(rootPpu!)")
                    
                    /*
                     * Batters section of the JSON
                     */
                    // 4. parse the 'batters' element
                    let battersDictionary = entireJson["batters"] as? NSDictionary
                    print("battersDictionary = \(battersDictionary!)")
                    
                    // 5. parse 'batter' element, expect an array
                    let batterArray = battersDictionary!["batter"] as? [NSDictionary]
                    print("batterArray = \(batterArray!)")
                    
                    // 6. iterate the batter array of dictionaries
                    for eachBatter in batterArray!
                    {
                        print("eachBatter = \(eachBatter)")
                        let eachBatterId = eachBatter["id"] as? String
                        let eachBatterType = eachBatter["type"] as? String
                        
                        //output result to console
                        print("eachBatterId = \(eachBatterId!)")
                        print("eachBatterType = \(eachBatterType!)")
                    }

                    
                    /*
                     * Toppings section of the JSON
                     */
                    let toppingArray = entireJson["topping"] as? [NSDictionary]
                    print("toppingArray = \(toppingArray!)")
                    
                    // interate the topping array of dictionaries
                    for eachTopping in toppingArray!
                    {
                        print("eachTopping = \(eachTopping)")
                        let eachToppingId = eachTopping["id"] as? String
                        let eachToppingType = eachTopping["type"] as? String
                        
                        //output result to console
                        print("eachToppingId = \(eachToppingId!)")
                        print("eachToppingType = \(eachToppingType!)")
                    }
                    
                }
                catch
                {
                    print("Error serializing JSON data")
                }
            }
            else
            {
                print("Error downloading JSON data. Error = \(error)")
            }
        }
        dataTask.resume()
    }
}
So....lets go by the numbers in the comments.

1 and 2. We make the GET call, and convert the NSData to a JSON Object. I covered that section extensively in a previous tutorial, so I won't get into it here.

3. We parse the root elements. These are elements that are at the root level, and not inside another JSON Object or JSON Array. There are only 4 of them in the sample JSON:
{

    "id": "0001",
    "type": "donut",
    "name": "Cake",
    "ppu": 0.55,
    // other stuff
} 
Notice the format used is: ["key"] as? String.
We use the key that comes from the JSON, the as? keyword which will allow nil values, and String is what we're expecting to get.
In one of the elements, ppu, we use as? Double, because we expect a Double type.

4. We move to the batters section:
{

    "id": "0001",
    "type": "donut",
    "name": "Cake",
    "ppu": 0.55,
    "batters": {
        "batter": [
    // more later
}
The batters section is an dictionary (AKA as map, or key-value pair), and we're actually expecting then an NSDictionary, we use this format:
["key"] as? NSDictionary

5. Inside of batters, we have objects that are "batter":[ ]. Remember that the [ ] is an array, and since we're using JSON format, our elements inside the array will always be a key-value pair.
This means, we have an array of dictionaries. In Apple-lingo, we call it an NSArray of NSDictionaries.
So we use this format for it:
["key"] as? [NSDictionary]

Since we're the 'batters' dictionary already, we do it like this:
myNextLevelJSONDictionary["key"] as? [NSDictionary]
This covers only this portion of the JSON:
"batters": {
    "batter": [

Note. Since this is an array of NSDictionaries, we could've also used this key:
["key"] as? [String:AnyObject]
Both formats work, although NSDictionary is preferred!

6. The array of dictionaries gave us...well, an array, and because of that we can iterate through it to get each element inside the array, individually.
In here I just used a regular for-each (or fast enumeration) loop.
Then, inside the loop I can get the values of each 'batter':
"batter": [
    {
    "id": "1001",
    "type": "Regular"
    },
With this, like format, like before:
eachBatter["key"] as? String

Then you repeat the recipe (no pun intended), and do it for Toppings.

I hope that helps!
Eduardo.

8 comments:

  1. Thanks for the share, Informative post - Check here the Google's top ranked site for AngularJS - http://www.credosystemz.com/training-in-chennai/best-angularjs-training-in-chennai/

    ReplyDelete
  2. Thanks for sharing the knowledge. So useful and practical for me. I learned something new. Very well written. It was so good to read and useful to improve knowledge. If you are looking for any Data science related information, check our Data science training institute in bangalore web page. Thanks a lot.

    ReplyDelete
  3. Thanks for sharing your innovative ideas to our vision. I have read your blog and I gathered some new information through your blog. Your blog is really very informative and unique. Keep posting like this. Awaiting for your further update. If you are looking for any Python programming related information, please visit our website python training institute in Bangalore

    ReplyDelete
  4. Superb. I really enjoyed very much with this article here. Really it is an amazing article I had ever read. I hope it will help a lot for all. Thank you so much for this amazing posts and please keep update like this excellent article.



    oracle training in chennai

    oracle training in annanagar

    oracle dba training in chennai

    oracle dba training in annanagar

    ccna training in chennai

    ccna training in annanagar

    seo training in chennai

    seo training in annanagar

    ReplyDelete
  5. Thanks for sharing your innovative ideas to our vision. I have read your blog and I gathered some new information through your blog. Your blog is really very informative and unique. Keep posting like this.
    hadoop training in chennai

    hadoop training in omr

    salesforce training in chennai

    salesforce training in omr

    c and c plus plus course in chennai

    c and c plus plus course in omr

    machine learning training in chennai

    machine learning training in omr

    ReplyDelete