This is a quick guide to parsing a JSON file in iOS, specifically on the iPhone. I’m not going to go into any detail about the history of JSON or why it’s used in preference to, say, XML. If you want to find out more about JSON then check out the following resources:
http://www.json.org/
http://www.w3schools.com/json/
The use of JSON as a serialisation format has grown in popularity as more and more third parties such as Google, Yahoo, and Twitter provide web services that return JSON formatted data from a given URL appended with a query string. For example, you can get the weather for London from the following URL and appended query (the “q=London,uk” part):
http://api.openweathermap.org/data/2.5/weather?q=London,uk
At the time of writing the returned JSON looks like this:
{ "coord": { "lon": -0.12574, "lat": 51.50853 }, "sys": { "country": "GB", "sunrise": 1383202413, "sunset": 1383237278 }, "weather": [ { "id": 800, "main": "Clear", "description": "Sky is Clear", "icon": "01n" } ], "base": "gdps stations", "main": { "temp": 285.6, "pressure": 1015, "humidity": 82, "temp_min": 284.26, "temp_max": 286.15 }, "wind": { "speed": 5.1, "deg": 210 }, "rain": { "3h": 0 }, "clouds": { "all": 0 }, "dt": 1383246611, "id": 2643743, "name": "London", "cod": 200 }
In order to make some sense of this, the iOS developer needs to parse this information and structure it into model data for their application so it can be usefully displayed. It turns out that from iOS 5 onwards, Apple have implemented a relatively simple framework for reading a JSON stream from a given URL (local or on-line) and parsing that stream into information that can be used in an iOS application.
For the following example I’ve put together a JSON file containing some information about six places of interest. Here’s what the file looks like:
{ "locations": [ { "title": "The Pump Room", "place": "Bath", "latitude": 51.38131, "longitude": -2.35959, "information": "The Pump Room Restaurant in Bath is one of the city’s most elegant places to enjoy stylish, Modern-British cuisine.", "telephone": "+44 (0)1225 444477", "url": "http://www.romanbaths.co.uk", "visited" : true }, { "title": "The Eye", "place": "London", "latitude": 51.502866, "longitude": -0.119483, "information": "At 135m, the London Eye is the world’s largest cantilevered observation wheel. It was designed by Marks Barfield Architects and launched in 2000.", "telephone": "+44 (0)8717 813000", "url": "http://www.londoneye.com", "visited" : false }, { "title": "Chalice Well", "place": "Glastonbury", "latitude": 51.143669, "longitude": -2.706782, "information": "Chalice Well is one of Britain's most ancient wells, nestling in the Vale of Avalon between the famous Glastonbury Tor and Chalice Hill.", "telephone": "+44 (0)1458 831154", "url": "http://www.chalicewell.org.uk", "visited" : true }, { "title": "Tate Modern", "place": "London", "latitude": 51.507774, "longitude": -0.099446, "information": "Tate Modern is a modern art gallery located in London. It is Britain's national gallery of international modern art and forms part of the Tate group.", "telephone": "+44 (0)20 7887 8888", "url": "http://www.tate.org.uk", "visited" : true }, { "title": "Eiffel Tower", "place": "Paris", "latitude": 48.858271, "longitude": 2.294114, "information": "The Eiffel Tower (French: La Tour Eiffel, is an iron lattice tower located on the Champ de Mars in Paris.", "telephone": "+33 892 70 12 39", "url": "http://www.tour-eiffel.fr", "visited" : false }, { "title": "Parc Guell", "place": "Barcelona", "latitude": 41.414483, "longitude": 2.152579, "information": "Parc Guell is a garden complex with architectural elements situated on the hill of El Carmel in the Gràcia district of Barcelona.", "telephone": "+34 902 20 03 02", "url": "http://www.parkguell.es", "visited" : false } ] }
To demonstrate parsing this JSON file, I’ve created a simple iPhone project that reads in the file, parses it and creates a “Location” class for each location entry declared in the JSON. This is then displayed as a list in a table view controller which then links to a detail view controller displaying more information together with a map and a map pin (annotation) showing the location of the selected place.
The example Xcode project is hosted at GitHub and you can either clone it or download it from here:
https://github.com/Phillipus/JSONHandler
(Note that I’ve set up this project to run on iOS 7. You’ll also need to be using Xcode 5.)
Load the project into Xcode and run it in the iOS simulator. When you see the first screen you’ll see a simple table displaying six location names and places:
Selecting a row in the table view initiates a segue to a new view controller that displays more detail about the location and a pin on a map:
Let’s examine how we load and display the raw JSON data in the application, starting at the point where the first table is displayed.
Open up the LocationsViewController.m class file and take a look at the viewDidLoad method:
- (void)viewDidLoad { [super viewDidLoad]; // Create a new JSONLoader with a local file URL JSONLoader *jsonLoader = [[JSONLoader alloc] init]; NSURL *url = [[NSBundle mainBundle] URLForResource:@"locations" withExtension:@"json"]; // Load the data on a background queue... // As we are using a local file it's not really necessary, but if we were connecting to an online URL then we'd need it dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ _locations = [jsonLoader locationsFromJSONFile:url]; // Now that we have the data, reload the table data on the main UI thread [self.tableView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:YES]; }); }
viewDidLoad is the method that’s called when the Table View Controller is first created and displayed, so this is a good place to load the JSON data file. First we create a new instance of the class JSONLoader, which we’ll look at soon, and then a URL which is the location of our JSON file. If the JSON file was located online you would create an online URL here, but for this example we’re using a local file as this ensures we can run the project without any connection issues. Once we’ve done this, we call the locationsFromJSONFile: method on the JSONLoader instance. This will return an NSArray of Location objects that we can then use as a data source to display in the table. Notice that the NSArray instance variable, _locations, has been declared in the first line of the implementation:
NSArray *_locations;
Once the array of Location objects has been returned by the call to locationsFromJSONFile:, we then ask the table view to reload its data. This will in turn trigger the call of the Table View Controller methods, tableView:cellForRowAtIndexPath: and tableView:numberOfRowsInSection: which will supply the information needed to display the rows in the table.
Notice that the method call locationsFromJSONFile: is dispatched on a queue. This ensures that the operation to connect to the URL and fetch the JSON data is performed on a background thread so that we don’t block the UI. Once the data has been received we can then load the data in the table on the main UI thread by calling performSelectorOnMainThread:withObject:waitUntilDone:. The golden rule in iOS is that all UIKit objects methods should be accessed on the main thread.
The over-ridden method prepareForSegue:sender: simply ascertains which row in the table was selected and passes on the corresponding Location object to the target destination View Controller. This destination view, LocationDetailViewController, then displays the information in the Location object in a set of text fields and as a pin on a map.
Now that we know how the JSON data is loaded and presented to the user in a table view, let’s look under the hood at the model classes that do the work of parsing the contents of the JSON file and turning it into something that we can use in the application. Open the class file, JSONLoader.m and look at the following code:
- (NSArray *)locationsFromJSONFile:(NSURL *)url { // Create a NSURLRequest with the given URL NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:30.0]; // Get the data NSURLResponse *response; NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil]; // Now create a NSDictionary from the JSON data NSDictionary *jsonDictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; // Create a new array to hold the locations NSMutableArray *locations = [[NSMutableArray alloc] init]; // Get an array of dictionaries with the key "locations" NSArray *array = [jsonDictionary objectForKey:@"locations"]; // Iterate through the array of dictionaries for(NSDictionary *dict in array) { // Create a new Location object for each one and initialise it with information in the dictionary Location *location = [[Location alloc] initWithJSONDictionary:dict]; // Add the Location object to the array [locations addObject:location]; } // Return the array of Location objects return locations; }
The NSURLRequest and NSURLConnection part of the code is pretty much the standard approach to getting data from a given URL. We are using the static method sendSynchronousRequest rather than an asynchronous call because the whole method is being called on a queue as we saw earlier. The magic happens in this line:
NSDictionary *jsonDictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
The NSJSONSerialization class has a static method, JSONObjectWithData:options:error, which parses an NSData object and returns a Foundation object. This is typically an NSDictionary or an NSArray depending on how the JSON data is structured. The example locations.json file is structured as a dictionary consisting of one key, “locations”, mapped to an array of dictionary values. We access the array with the following line:
NSArray *array = [jsonDictionary objectForKey:@"locations"];
We can then iterate through the NSDictionary objects in the array creating one Location class object for each dictionary entry:
Location *location = [[Location alloc] initWithJSONDictionary:dict];
We then add this to a NSMutableArray of locations and return it when the method finishes.
Here’s the Location class header file:
@interface Location : NSObject - (id)initWithJSONDictionary:(NSDictionary *)jsonDictionary; @property (readonly) NSString *title; @property (readonly) NSString *place; @property (readonly) NSNumber *latitude; @property (readonly) NSNumber *longitude; @property (readonly) NSString *information; @property (readonly) NSString *telephone; @property (readonly) NSString *url; @property (readonly) NSNumber *visited; @end
And the Location class implementation file:
#import "Location.h" @implementation Location // Init the object with information from a dictionary - (id)initWithJSONDictionary:(NSDictionary *)jsonDictionary { if(self = [self init]) { // Assign all properties with keyed values from the dictionary _title = [jsonDictionary objectForKey:@"title"]; _place = [jsonDictionary objectForKey:@"place"]; _information = [jsonDictionary objectForKey:@"information"]; _telephone = [jsonDictionary objectForKey:@"telephone"]; _latitude = [jsonDictionary objectForKey:@"latitude"]; _longitude = [jsonDictionary objectForKey:@"longitude"]; _url = [jsonDictionary objectForKey:@"url"]; _visited = [jsonDictionary objectForKey:@"visited"]; } return self; } @end
The Location class is simply a set of properties that map to the key names that are found in the location.json file. If you recall, the first set of data looks like this:
"title": "The Pump Room", "place": "Bath", "latitude": 51.38131, "longitude": -2.35959, "information": "The Pump Room Restaurant in Bath is one of the city’s most elegant places to enjoy stylish, Modern-British cuisine.", "telephone": "+44 (0)1225 444477", "url": "http://www.romanbaths.co.uk", "visited" : true
So, by writing code like this:
_title = [jsonDictionary objectForKey:@"title"];
we are effectively assigning values to the properties in the Location class:
title = “The Pump Room”
place = “Bath”
and so on. (Note that the instance variables are prefixed with underscores. Because the properties are readonly we can only assign values directly to the underlying instance variables which are automatically synthesised for us).
We now have all of the information for a location encapsulated conveniently in a model class, Location.
If we take a look at the LocationDetailViewController.m class file we can see that in the viewDidLoad method we access all of these properties to populate the fields in the view. Note that the boolean value for “visited” is a NSNumber type which has to be converted to a BOOL type in order to set the value for the UISwitch control. The latitude and longitude properties are also NSNumber objects and these have to be converted to double values to create the coordinates for the map pin.
Wrap up
This post has, I hope, given you a start with parsing JSON and mapping the data to a simple model class. Of course, the structure and hierarchy of your own model classes will depend on the structure of the data in whatever JSON file or stream that you are working with.