Episode #129

Swift JSON

13 minutes
Published on July 24, 2014

This video is only available to subscribers. Get access to this video and 573 others.

Parsing JSON (which provides no contracts or type guarantees) can be difficult and tedious in Swift. Many of the problems you are forced to deal with were easier to ignore in Objective-C, but that doesn't mean they weren't present. In this episode we'll take a look at a very manual approach to mapping from a JSON response to a Swift type.

Episode Links

Parsing The Raw Data Into a JSON Object

The return type of this call is AnyObject! so we either have to guess that the response will be an array (and deal with a crash if it is not) or do type checking with as? in order to be a little safer:

func parseJson(data: NSData, completion: JSONArrayCompletion) {
    var error: NSError?
    let json: AnyObject = NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments, error: &error)
    if error {
      println("Error parsing json: \(error)")
      completion(nil)
    } else {
      completion(json)
    }
  }

The completion block will take this AnyObject json variable and do the casting:

func parseEpisodes(json: AnyObject) -> [Episode] {
  var episodes: [Episode] = []
  if let array = json as? [ [ String : AnyObject ] ] {
    for container in array {
      let item: AnyObject? = container["episode"]
      if let dict = item as? [ String : AnyObject ] {
        if let episode = parseEpisode(dict) {
          episodes.append(episode)
        }
      }
    }
  } else {
    println("expected an outer array")
  }
  return episodes
}

Here we check to see if the json object is castable as an array of dictionaries, specifically a dictionary with a String key and AnyObject value. If it is not, we print an error and return an empty array.

If we have an array, we loop over it, pulling out a container dictionary (because each item in the response has an "episode" key). For each we convert the raw dictionary to an episode.

Converting from a Dictionary to a Swift type

func parseEpisode(dict: [ String : AnyObject ]) -> Episode? {
  if let id = dict["id"] as? NSNumber {
    if let title = dict["title"] as? NSString {
      if let description = dict["description"] as? NSString {
        if let number = dict["episode_number"] as? NSNumber {
          return Episode(id: id.integerValue(),
              title: title,
              episodeDescription: description,
              number: number.integerValue())
        }
      }
    }
  }
  return nil
}

Here we check each key to see if it is present _and_ castable as the type we're expecting.  We are using the more flexible `NSNumber`, `NSString`, etc because our dictionary is defined as `[ String : AnyObject ]`.  `AnyObject` covers classes but not structs like `Int` or `String`, so we get compiler errors if we try to cast as those types.

If all of our keys are in order, we build the episode struct with the values.