Compiled Chronicles

A software development blog by Angelo Villegas

Swift: JSON Parsing

JavaScript Object Notation.

JSON, pronounced (/ˈdʒeɪsən/ jay-sən), is an easy standard to parse in client-server application data. It is easy to implement and simple to understand. With the release of iOS 5 back in 2011, the NSJSONSerialization class was added in the SDK, which gives an easy to use delegate method to parse JSON data.

What is JSON?

JSON is a text-based, lightweight and easy way for delivering data. It’s commonly used for representing structural data and/or data interchange in client-server applications, serving as a better alternative to XML. A lot of the services we use everyday have JSON-based APIs. Most of the iOS apps including Twitter and Apple’s iTunes send data from their backend web services in JSON format.

As the lightweight implies, JSON in all it’s greatness, comes with limitations, some of it are:

  • JSON generally ignores any whitespace around or between syntactic elements (excluding, of course, string values).
  • JSON only recognizes four specific whitespace characters: spacehorizontal tabline feed, and carriage return.
  • JSON does not provide or allow any sort of comment syntax.

JSON is built on two structures: a collection of key/value pairs, and an ordered list of values. In various languages, key/value pairs is realised as an object, record, struct, dictionary, hash table, keyed list, or associative array while an ordered list, in most languages, is realised as an array, vector, list, or sequence.

JSON Form
NameValueDescription
object{}
{ members }
An object is an unordered set of key/value pairs. An object begins with { and ends with }. Each name is followed by : and the key/value pairs are separated by ,.
array[]
[ elements ]
An array is an ordered collection of values. An array begins with [ and ends with ]. Values are separated by ,.
valuestring number object array true false nullvalue can be a string in double quotes, or a number, or true or false or null, an object or an array. These structures can be nested.
string“” ” chars “string is a sequence of zero or more Unicode characters, wrapped in double quotes "", using backslash escapes \. A character is represented as a single character string. A string is very much like a C or Java string.
numberint int frac int exp int frac expnumber is very much like a C or Java number, except that the octal and hexadecimal formats are not used

If you write your own web service (personal, client, or company), there’s a high chance JSON will be the return format of the data being sent.

What it looks like

If you have an array of three strings, the JSON representation would simply be:

["test1", "test2", "test3"]

If you have a Person object with member variables firstName, lastName, and age, the JSON representation would simply be:

{
  "firstName": "John",
  "lastName": "Smith",
  "age": 25
}

It’s that simple, which is why it’s so easy and popular to use. For the full spec, which can be read in just a couple minutes, check out www.json.org.

Understanding JSON Parsing

Anything surrounded with `{}` is a dictionary, and anything surrounded with `[]` is an array.

One of the most important task a developer has to deal with when creating network-connected applications is the data handling and manipulation. Data sent to your application can have different formats other than JSON but speaking for mobile applications, specifically now that it is quite common for them to exchange data, a lightweight format such as JSON is better in most cases and used by many.

Before, there are a lot of JSON libraries for iOS that were written mostly by other developers like you. Since iOS 5, the iOS SDK provides it’s own classes for handling JSON known as NSJSONSerialization. The class is very simple and straightforward to be used. In order to convert a JSON data into a Foundation form, it has to be an NSData object.

Translation

As you may have already noticed on the table above, JSON objects are different and the same with Foundation objects. The below table will give you the exact equivalent of JSON objects with the Foundation framework that NSJSONSerialization will return after parsing the data.

JSONFoundation EquivalentDescription
Objective-CSwift
objectNSDictionaryDictionaryAn object is an unordered set of name/value pairs. An object begins with { (left brace) and ends with } (right brace). Each name is followed by : (colon) and the name/value pairs are separated by , (comma).
arrayNSArrayArrayAn array is an ordered collection of values. An array begins with [ (left bracket) and ends with ] (right bracket). Values are separated by , (comma).
valueNSString NSNumber NSDictionary NSArray NSNullString Numbers Dictionary Array nilA value can be a string in double quotes, or a number, or true or false or null, or an object or an array. These structures can be nested.

Naïve Approach

Okay, first things first, it’s not a technical term with a precise meaning, it is just the English word naïve.

In a computer science context, this usually means the approach one takes is usually the first one the person thinks. Finding a word in a dictionary (the book) for example, the naïve approach is to start searching for the word starting from the beginning, and look for the first word; if it’s not the word you’re looking for, you go to the next one, and repeat.

better implementation is to start somewhere near, or at least we think is near, to the actual word we are searching for. A good example is if we’re looking for the word “neanderthals”, we would probably open the book on the middle — if the word comes before, we would go back, otherwise we would go forward.

Dangers of Naïve Approach

Usually, naïve approaches are not wrong, just inefficient (and sometimes takes longer code to write or longer to process), and that there are some algorithms that should not even be approached naïvely. For our dictionary algorithm above for example, if we took the naïve root, it would be time consuming to find the word “neanderthals” than the better approach.

As all approaches have, the non-naïve approach comes with limitations; if the dictionary isn’t sorted alphabetically, the “better” method won’t work but the naïve one will.

Naïve Parsing

You should expect from many seasoned developers the naïve approach of parsing JSON data. Coming from Objective-C, most often than not, parsing the JSON data above will have a code similar to the code below:

NSArray *parsedArray = [parsed objectForKey:@"arrayData"];
for(int i = 0; i < parsedArray.count; i++) {
  NSString *test = parsedArray[i];
}

NSDictionary *parsedDictionary = [parsed objectForKey: @"dictionaryData"];
NSString *firstName = parsedDictionary[@"firstName"];
NSString *lastName = parsedDictionary[@"lastName"];
NSString *age = parsedDictionary[@"age"];

This approach has been inherited by old developers after the release of Swift last year that even new developers are probably using the same approach.

if let json = dictionaryData as? Dictionary<String, String> {
  if let firstName = json["firstName"] as String {
    if let lastName = json["lastName"] as String {
      if let age = json["age"] as String {
        let user = User(firstName: firstName, lastName: lastName, age: age)
      }
    }
  }
}

Have you seen it? Of course, different language means different syntax even though they have the same algorithm. If you know PHP or any other similar languages, you most probably have seen it AND know about it that you might have cringe a little. It’s the rightward drift of if statements to check if the variable or an object has a value. This leads to the optional-checking tree, the so-called Swift’s pyramid of doom.

Improved Optional Binding

As of Swift 1.2, multiple optional bindings has been allowed. This provides developers to escape the dreaded pyramid from Swift’s trap of deeply nested if statements to unwrap multiple optional values. It even utilises a where clause that is used to test for boolean conditions inline.

if let
  json = dictionaryData as? Dictionary<String, String>
  where dataCount(json) < actualDataCount,
  firstName = json["firstName"] as String,
  lastName = json["lastName"] as String,
  age = json["age"] as String
{
  let user = User(firstName: firstName, lastName: lastName, age: age)
}

This makes conditionals much more compact and will avoid, if not remove completely, the rightward drift indentation of the so-called pyramid of doom. You can even reference the earlier bindings with the later binding expressions. This means you can go into Dictionary instances or cast an AnyObject? value to a specific type, then use it in another expression, all in a single if let statement.

Parsing JSON

Let’s assume this is the JSON data, which is located on a remote site:

[
  {
    "name": "Keynote",
    "tag": "Featured",
    "description": "WWDC 2015 Keynote",
    "location": "Presidio",
    "date": "2015-06-08",
    "startTime": "10:00",
    "endTime": "12:00"
  },
  {
    "name": "Lunch",
    "tag": "",
    "description": "",
    "location": "Lunch Area",
    "date": "2015-06-08",
    "startTime": "12:15",
    "endTime": "13:15"
  },
  {
    "name": "Platforms State of the Union",
    "tag": "Featured",
    "description": "WWDC 2015 Platforms State of the Union",
    "location": "Presidio",
    "date": "2015-06-08",
    "startTime": "14:30",
    "endTime": "16:00"
  },
  {
    "name": "Apple Design Awards",
    "tag": "Featured",
    "description": "Join us for an unforgettable award ceremony celebrating developers and their outstanding work. The 2015 Apple Design Awards recognize state of the art iOS, OS X, and Apple Watch apps that reflect excellence in design and innovation.",
    "location": "Presidio",
    "date": "2015-06-08",
    "startTime": "16:30",
    "endTime": "17:30"
  }
]

The JSON data is a bit longer than the first two data from above, but if you look closer, this JSON data type is just the two previous combined. In iOS speak, it’s an NSDictionary inside an NSArray.

If you’re familiar with Apple’s WWDC 2015 schedule, then you’ve probably seen the data (memorised it, even).

Not utilising the improved optional binding added to Swift, the JSON data above will most likely make seasoned developers cringe at the thought of parsing it.

The all new improved optional binding significantly reduces the rightward drift of most, if not all, if let statements. An if statement can now include multiple comma-separated let statements. Just remember that all expression inside a single if let statement, including the inline boolean conditions, must succeed in order for the conditional clause to be evaluated.

var scheduleList: [Schedule] = []

if let
  path     = NSBundle.mainBundle().pathForResource("schedule", ofType: "json"),
  url      = NSURL(fileURLWithPath: path),
  data     = NSData(contentsOfURL: url),
  schedules = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil) as? [[String: String]]
{
  for schedule in schedules {
    if let
      name          = schedule["name"],
      tag           = schedule["tag"],
      description   = schedule["description"],
      location      = schedule["location"],
      date          = schedule["date"],
      startTime     = schedule["startTime"],
      endTime       = schedule["endTime"]
    {
      scheduleList.append(Schedule(name: name, tag: tag, description: description, location: location, date: date, startTime: startTime, endTime: endTime))
    }
  }
}

The example above uses one if let statement to handle the optionals that come with using NSBundleNSURLNSData, and NSJSONSerialization, then another to handle the assignment of several String instances from the interpreted JSON. Although the syntax is slightly better than the previous rightward drift, they are still semantically equivalent — a fairly unpleasant mix of model structure and parsing logic.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *