Reading from a Dictionary in Swift

We all have different ways of learning new things. Like a new language, new APIs, new frameworks, etc. For me, like many others, I learn by doing at least 2 things:

  • Work on a real project.
  • Help others.

I got the opportunity to work on a project built in Swift. A project that eventually will be released on App Store so I got the first point covered.

As for nr. 2, I keep myself updated on different forums, looking for new questions to answer on Stackoverflow, commenting on blogs, pair-programming, etc.

While doing this I found that a common question is how to read data from a Dictionary in Swift. Why you cannot downcast your values to certain data types. Why your ported Objective-C code gives you compile errors when reading from your Dictionary.

So I thought I’d cover some of the answers I’ve given on different forums in this post.

The sample code for today’s adventure can be found in GitHub.

The setup

The idea is that we have an app that communicates with a backend.

In this example we want to get a list of employees for a company. I have left out the network communication and put the response in a static file because it’s not really interesting in this example.

The response is in JSON and looks like this:

{ "employees": [ { "firstName": "John", "lastName": "Doe" }, { "firstName": "Anna", "lastName": "Smith" }, { "firstName": "Peter", "lastName": "Jones" } ]}

Parsing in Objective-C

I’m not going to focus too much on how you get the data. You’re most likely already familiar with communicating with a backend and probably using a third-party library like AFNetworking to fetch the data.

In this example we have a method that will read our .json file and parse it to an NSDictionary.

- (NSDictionary *)jsonResponse{ NSString *path = [[NSBundle mainBundle] pathForResource:@"data" ofType:@"json"]; NSData *data = [NSData dataWithContentsOfFile:path]; return [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];}

Pretty straight-forward. Read the file, parse it to a JSON object and return it as an NSDictionary. This is what most third-party network libraries does for you.

Let’s instead look at how we use the NSDictionary to parse the data we are interested in.

First off we want to get our data and log it to make sure we have everything we need:

NSDictionary *json = [self jsonResponse];NSLog(@"JSON: %@", json);// the log will show:JSON: { employees=({ firstName=John; lastName=Doe; }, { firstName=Anna; lastName=Smith; }, { firstName=Peter; lastName=Jones; });} 

Looks good. Reading our .json file works and all of the data is now available through our json dictionary.

What we want to do now is to loop the array of dictionaries and print each of the employee’s name.

First get the employees and cast it to an NSArray:

NSArray *employees = json[@"employees"];

Then for each employee we want to get the firstName and lastName then log it to make sure we have the expected values:

for (NSDictionary *employee in employees){ NSString *firstName = employee[@"firstName"]; NSString *lastName = employee[@"lastName"]; NSLog(@"employee: %@ %@", firstName, lastName);}

This will log the following:

employee: John Doeemployee: Anna Smithemployee: Peter Jones

Nothing fancy. Everything is pretty straight-forward and you have probably already done this many times before.

Now to the fun part. Let’s look at how to port this code to Swift.

Parsing in Swift

Our jsonResponse() method is slightly different in Swift.

The dataWithContentsOfFile: method we use in Objective-C is deprecated in Swift so instead we use: dataWithContentsOfFile:options:error:

The other problem is that in Swift the AnyObject is not convertible to Dictionary so we have to downcast before we return.

Not a big deal. The complete method looks like this:

func jsonResponse() -> [String : AnyObject] { let path = NSBundle.mainBundle().pathForResource("data", ofType: "json") let data = NSData.dataWithContentsOfFile(path, options: NSDataReadingOptions.DataReadingUncached, error: nil) let json: AnyObject! = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) return json as [String : AnyObject]}

Let’s go through each step as we did in the Objective-C section.

First is to get our json response and log it:

let json = jsonResponse()println("JSON: \(json)")// the log will show:JSON: [employees: ( { firstName = John; lastName = Doe; }, { firstName = Anna; lastName = Smith; }, { firstName = Peter; lastName = Jones; })]

Looks good. Next is to get the employees and cast it to an array of dictionaries:

let employees = json["employees"] as [[String : AnyObject]]

Build errors. Say whaat?

'(String, AnyObject)' is not convertible to '[[String : AnyObject]]'

Alright, maybe it’s nil and we need to do a conditional downcast?

if let employees = json["employees"] as? [[String : AnyObject]] {

Nope. Still not building and we got a different error:

'[[String : AnyObject]]' is not a subtype of '(String, AnyObject)'

Why can I build this in Objective-C and not in Swift? Sure it’s the same thing right?

Let’s look at what’s going on.

Dictionary vs NSDictionary

Believe it or not but you see these errors because of the code safety improvements they have done in Swift.

Consider the following in Objective-C:

NSMutableDictionary *data = [@{ @"key" : @"value" } mutableCopy];data[@"key"] = nil;

Setting a nil value in a collection is not allowed in Objective-C. If you run the code above your application will crash with:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** setObjectForKey: object cannot be nil (key: key)'

This is where it differs in Swift. Setting a nil value in a mutable Dictionary does nothing if there’s not a value for that key already.

If there’s already a value for the key then the value will be removed.

var data = ["key" : "value"]data["key"] = nil // this will remove "value"data["key"] = nil // this will do nothing because there's no value for "key"data["key"] = "value" // this will set "value"

Because we can set a nil value in a Dictionary it means we can also get a nil value.

In order to set and get nil values in Swift the data type has to be an optional.

If we look at the Dictionary implementation we can see that the subscript is returning a ValueType optional:

subscript (key: KeyType) -> ValueType?

So whenever we get a value from our Dictionary we get it as an optional from the subscript. Meaning we have to unwrap the optional to get the underlying object.

Let’s go back to our parser and try it out!

The Swift parsing solution

Now that we know the subscript returns an optional we can unwrap it to get our employees array:

let employees = json["employees"]! as [[String : AnyObject]]

No build errors!

Next step is to loop the employees array and print the firstName and lastName.

Keep in mind that we are looping an array of dictionaries. Meaning that in order to get the String values from firstName and lastName we need to unwrap the optionals first:

for employee in employees { let firstName = employee["firstName"]! as String let lastName = employee["lastName"]! as String println("employee: \(firstName) \(lastName)")}

Build, run and you should see:

employee: John Doeemployee: Anna Smithemployee: Peter Jones

Where to go from here?

This covers how you can resolve some of the common issues when reading from a Swift Dictionary.

Keep in mind that this is sample code. If you get a nil value back from your Dictionary, and you try to unwrap it, the application will crash.

In production code I would suggest you to put your optional unwrapping in an if-let statement:

if let employees = json["employees"]! as? [[String : AnyObject]] { // safe to use employees}

Also be sure to check out SwiftyJSON, a nice third-party library that makes JSON parsing in Swift a bit easier.

That’s all for today. Please get in touch if you have any questions or feedback!