Parsing JSON

The most popular mechanism to send structured data over a network is to encode it in JSON, which stands for JavaScript Object Notation. This provides a hierarchical tree data structure that can store simple numeric, logical, and string-based types along with the array and dictionary representations.

Both Mac OS X and iOS come with a built-in parser for JSON documents, in the NSJSONSerialization class. This provides a means to parse a data object and return an NSDictionary that contains the key/value pairs of a JSON object or an NSArray to represent JSON arrays. Other literals are parsed and represented as the NSNumber or NSString values.

The JSON parser uses JSONObjectWithData to create an object from an NSData object containing a string. This is typically the format returned by network APIs and can be created from an existing string using dataUsingEncoding with one of the built-in encoding types, such as NSUTF8StringEncoding.

A simple JSON array of numbers can be parsed as follows:

let array = "[1,2,3]".dataUsingEncoding(NSUTF8StringEncoding)!
let parsed = NSJSONSerialization.JSONObjectWithData(
  data:array, options:nil, error:nil)

The return type of this is an optional AnyObject. The optionality represents the fact that the data content might not be valid JSON data. It can be cast to an appropriate type using the as keyword; if there is a parsing failure, then an error will be thrown.

The options can be used to indicate whether the return type should be mutable or not. Mutable data allows the caller to add or delete items after being returned from the parsing function; if not specified, the return value will be immutable. The NSJSONReadingOptions options include MutableContainers (containing data structures that are mutable), MutableLeaves (the child leaves are mutable), and AllowFragments (allow non-object, nonarray values to be parsed). Since these are bit flags, they can be combined with | (the bitwise or operator). For example, to specify that both the containers and leaves should be mutable, .MutableContents|.MutableLeaves should be used as the options value.

The SampleTable.json file (referred to in the viewDidLoad method) stores an array of entries, with the title and content fields holding text data per entry:

[{"title":"Sample Title","content":"Sample Content"}]

To parse the JSON file and entries to the table, replace the default clause in SampleTable with the following:

default:
  let parsed = NSJSONSerialization.JSONObjectWithData(
    data, options:nil, error:nil) as NSArray!
  for entry in parsed {
    self.items += 
      [(entry["title"] as String,
        entry["content"] as String)]
  }

Running the application will show the Sample Title and Sample Content entries in the table, which have been loaded and parsed from the book's GitHub repository.

Handling errors

If there are problems parsing the JSON data, then the return type of the JSONObjectWithData function will return a nil value. If the type is implicitly unwrapped then accessing the element will cause an error.

The error is known as an inout argument; by passing a reference to an optional NSError value, the function can assign an instance in addition to the normal return value:

var error:NSError? = nil
if let parsed = NSJSONSerialization.JSONObjectWithData(data,
 options:nil, error:&error) {
  // do something with parsed
} else {
  self.items += [("Error", 
   "Cannot parse JSON (error!.localizedDescription)")]
  // show message to user
}

The optional error is passed into the function with an & (ampersand) symbol. In C, this is used to pass the address of an object, but in Swift, this is limited to inout function parameters. It's mainly used for interoperability with existing C or Objective-C functions; generally using Option to indicate errors is the preferred way in Swift. When the function returns, the error will be not nil if parsed is nil and vice versa.

Tip

Since the error is declared an optional value, it must be forcefully unwrapped when being processed in the else clause. This is done with error! in the string literal that calculates localizedDescription.

The parsed value will be of the type AnyObject?, although the let block will implicitly unwrap the value, known as optional binding. In the previous section, the code was cast to an NSArray directly, but if the returned result is of a different type (for example, an NSDictionary or one of the fragment types such as NSNumber or NSString), then casting to an incorrect type will cause a failure.

The type of the object can be tested with if [object] is [type]. However, since the next step is usually to cast it to a different class with as, an as? shorthand form can perform both the test and the cast in one step:

if let array = parsed as? NSArray {
  for entry in array {
    // process elements
  }
} else {
  self.items += [("Error", "JSON is not an array")]
}

A switch statement can be used to check the type of multiple values at the same time. Since the values are optional NSString objects, they need to be converted to a String before they can be used in Swift:

for entry in array {
  switch (entry["title"], entry["content"]) {
    case (let title as String, let content as String):
      self.items += [(title,content)]
    default:
      self.items += [("Error", "Missing unknown entry")]
  }
}

Now when the application is run, errors are detected and handled without the application crashing.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset