binpress

SwiftyJSON: How to Handle JSON in Swift

Since Swift is very strict about types, it can be a hassle working with JSON because it’s naturally implicit about types. SwiftyJSON is an open source library that helps you use JSON in Swift without tearing your hair out. Before getting started, let’s take a closer look at just how painful handling JSON in Swift can be.

The Problem with JSON in Swift

Take the Twitter API for example. Retrieving a user’s “name” value from a tweet in Swift should be pretty simple. Here’s what we’re dealing with in JSON:

  1. [
  2.   {
  3.     ......
  4.     "text": "just another test",
  5.     ......
  6.     "user": {
  7.       "name": "OAuth Dancer",
  8.       "favourites_count": 7,
  9.       "entities": {
  10.         "url": {
  11.           "urls": [
  12.             {
  13.               "expanded_url": null,
  14.               "url": "http://bit.ly/oauth-dancer",
  15.               "indices": [
  16.                 0,
  17.                 26
  18.               ],
  19.               "display_url": null
  20.             }
  21.           ]
  22.         }
  23.       ......
  24.     },
  25.     "in_reply_to_screen_name": null,
  26.   },
  27.   ......]

In Swift, you’d have to use this mess:

  1. let jsonObject : AnyObject! = NSJSONSerialization.JSONObjectWithData(dataFromTwitter, options: NSJSONReadingOptions.MutableContainers, error: nil)
  2. if let statusesArray = jsonObject as? NSArray{
  3.     if let aStatus = statusesArray[0] as? NSDictionary{
  4.         if let user = aStatus["user"] as? NSDictionary{
  5.             if let userName = user["name"] as? NSDictionary{
  6.                 //Finally We Got The Name
  7.  
  8.             }
  9.         }
  10.     }
  11. }

Or, you could take another approach, but it’s hardly easy to read:

  1. let jsonObject : AnyObject! = NSJSONSerialization.JSONObjectWithData(dataFromTwitter, options: NSJSONReadingOptions.MutableContainers, error: nil)
  2. if let userName = (((jsonObject as? NSArray)?[0] as? NSDictionary)?["user"] as? NSDictionary)?["name"]{
  3.   //What A disaster above
  4. }

Getting Started

You can download SwiftyJSON here, or clone it directly from GitHub:

  1. git clone https://github.com/lingoer/SwiftyJSON.git

Basic Usage

Using SwiftyJSON is quite simple:

Say we have some dataFromNetwork: NSData! produced by a typical NSURLSessionTask fetching the Twitter’s API:

The first thing you should do is to init a JSONValue:

  1. let json = JSONValue(dataFromNetwork)

JSONValue is an enum that represents a typical JSON data structure.

You can retrieve different values from the original JSONValue just by using subscripts like this:

  1. let userName:JSONValue = json[0]["user"]["name"]

Note the userName is still a JSONValue. So, what if we want a String?

You can use the .string property to get the actual value of the JSON data it represents.

  1. let userNameString = userName.string!

For each kind of JSON Type, JSONValue provides a property to retrieve it:

  1. var string: String?
  2. var number: NSNumber?
  3. var bool: Bool?
  4. var array: Array<JSONValue>?
  5. var object: Dictionary<String, JSONValue>?

Note that every property above is an Optional value. This is because the JSON data can contain any type that’s valid in its definition.

So, the suggested way to practically retrieve value is by using Optional Bindings:

  1. if let name = userName.string{
  2.     //This could avoid lots of crashes caused by the unexpected data types
  3. }
  4.  
  5. if let name = userName.number{
  6.     //As the value of the userName is Not a number. It won't execute.
  7. }

The .number property produces an NSNumber value, which is usually not very useful in Swift. You can use the .double and the .integer to get a Double value or an Int value.

  1. if let intValue = numberValue.integer{
  2.     count += intValue
  3. }

Enumeration

The JSONValue is in fact an enum in Swift:

  1. enum JSONValue {
  2.  
  3.     case JNumber(NSNumber)
  4.     case JString(String)
  5.     case JBool(Bool)
  6.     case JNull
  7.     case JArray(Array<JSONValue>)
  8.     case JObject(Dictionary<String,JSONValue>)
  9.     case JInvalid(NSError)
  10.  
  11. }

You can use a switch statement to get values more efficiently:

  1. let json = JSONValue(jsonObject)
  2. switch json["user_id"]{
  3. case .JString(let stringValue):
  4.     let id = stringValue.toInt()
  5. case .JNumber(let numberValue):
  6.     let id = numberValue.integerValue
  7. default:
  8.     println("ooops!!! JSON Data is Unexpected or Broken")

Subscripts

Note that an Array structure in JSON is wrapped into Array<JSONValue>, which means that everything in the Array is still a JSONValue. Even if you retrieve an array from JSONValue, you still have to use the basic properties to get the element’s value:

  1. if let array = json["key_of_array"].array{
  2.     if let string = array[0].string{
  3.         //The array[0] is still a JSONValue!
  4.     }
  5. }

The same with the Object. So, the suggested way to access such Arrays and Objects is to always use JSONValue‘s subscripts.

  1. if let string = json["key_of_array"][0].string{
  2.  
  3. }

In fact you can always use subscripts to access a JSONValue without worrying about runtime errors leading to a crash:

  1. let userName = json[99999]["wrong_key"]

And if you use the suggested way to retrieve values, it always safe:

  1. if let userName = json[99999]["wrong_key"]["name"].string{
  2.     //It's always safe
  3. }

Printing

The JSONValue conforms to the protocol Printable. So it’s easy to get all the JSON data in raw string representation:

  1. let json = JSONValue(dataFromNetwork)
  2. println(json)
  3. /*You can get a well printed human readable raw JSON string:
  4.       {
  5.         "url": {
  6.           "urls": [
  7.             {
  8.               "expanded_url": null,
  9.               "url": "http://bit.ly/oauth-dancer",
  10.               "indices": [
  11.                 0,
  12.                 26
  13.               ],
  14.               "display_url": null
  15.             }
  16.           ]
  17.        }
  18. */

If you don’t want to just print it out, you can use the .description property to get the string above.

  1. let printableString = json.description

Debuging And Error Handling

What if JSON data is corrupted or if we retrieve data the wrong way? You can simply use an ifstatement to test it:

  1. let json = JSONValue(dataFromNetworking)["some_key"]["some_wrong_key"]["wrong_name"]
  2. if json{
  3.   //JSONValue it self conforms to Protocol "LogicValue", with JSONValue.JInvalid stands for false and others stands true
  4. }

If we try to access data through an incorrect key or index, the description can tell you where the KeyPath went wrong.

  1. let json = JSONValue(dataFromNetworking)["some_key"]["some_wrong_key"]["wrong_name"]
  2. if json{
  3.  
  4. } else {
  5.   println(json)
  6.   //> JSON Keypath Error: Incorrect Keypath "some_wrong_key/wrong_name"
  7.   //It always tells you where your key went wrong
  8.   switch json{
  9.   case .JInvalid(let error):
  10.     //An NSError containing detailed error information
  11.   }
  12. }

Final Note

Development on SwiftyJSON will continue here on Github, so keep an eye on it for the latest version.

Author: Roy Fu

Scroll to Top