Using Swift JSON parsing and optional binding to initialize and validate a struct

There are a number of posts that have discussed the use of Swift optional binding when parsing JSON data. The consensus seems to be that the syntax is at best ungainly. Here are some of the approaches on this with a couple of solutions:

We wanted to take a look at a use case where the native approach to handling JSON data is an advantage - initializing a Swift struct with data from a JSON resource.

JSON is often used as a data format for REST URLs. Using this data presents a couple of challenges to the iOS developer:

  • The data is very weakly typed
  • The data may be partial
  • The data may not come back at all if the URL points at a resource that is not available.

Let's take as an example a data service that returns an array of objects like this:

[
  {"name": "ABC", "amount": 330.50, "description": "The ABC Company"},
  {"name": "DEF", "amount": 340.50}
]

Note that the data has an optional property called description.

Let's say we want to use this JSON resource to initialize a structure that looks like this:

struct Asset {
  var name: String
  var description: String
  var amount: Double
}

We can take advantage of Swift optional binding to use the JSON data to initialize this struct and take into account that some of our JSON objects will lack a description property.

let urlAsString = "http://rest.dev/jsonrest.json" 
let url = NSURL(string: urlAsString)!
let urlSession = NSURLSession.sharedSession()

let jsonQuery = urlSession.dataTaskWithURL(url, completionHandler: {data, response, error -> Void in

  var err: NSError? 

  var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as NSArray

  if (err != nil) {
    println("JSON Error \(err!.localizedDescription)")
  }

  if let record = jsonResult[0] as? NSDictionary {
    // if the first item in the Array exists and is a Dictionary
    // then we can use it to initialize an Asset
    var a = Asset(fromDictionary: record)
  }
})

jsonQuery.resume()

In the above code listing, we retrieve a JSON array of objects from a URL, get the first object in the array and use it to initialize an instance of the Asset struct.

In the definition of the struct, we can define a custom initializer that provides default values and validation and take advantage of the optional binding.

struct Asset {
  var name: String
  var description: String
  var amount: Double

  init(fromDictionary d: NSDictionary) {

    // provide default values
    name = ""
    amount = 0.0
    description = "no description provided"

    // use optional binding the selectively populate the properties
    if let n = d["name"] as? NSString {
      name = n
    }
    if let a = d["amount"] as? NSNumber {
      amount = a.doubleValue
    }
    if let d = d["description"] as? NSString {
      description = d
    }
  }
}

As a result, when a JSON object is passed into the initializer the initializer will always ensure that there is a value for each property and will use the default value when a JSON object which lacks a description is provided.