August 27th, 2009

Using JSON WebServices

iPhone, by BigSprocket.

The game I’m working on right now is going to have downloadable content, some available for free, some for purchase. The latter will require me to code against Store Kit, and when I get there, I’ll post on my progress. Before that, though, I need a way to determine what content is available. The logical choice for this is a Web Service.
Once upon a time, all Web Services were XML-based. If you’re in the .NET world, you’re probably creating and consuming XML web services, because the environment makes it easy to do so. But, JSON web services are gaining traction, because the JSON format is lighter weight and easier to parse by both computers and humans.

The use of JSON on the iPhone is made incredibly easy by the use of the JSON Framework available on Google Code. While this framework does lots of good stuff (including auto-generating JSON representation for your objects), what I’m going to focus on now is its ability to render JSON streams as Arrays or Dictionaries.

Ready to see it in action? Me too … but before we get there, let’s just write ourselves a simple Array/Dictionary browser… it’ll come in handy when we get to the JSON part.

Start by creating a new iPhone window-based project. Call it JsonExample.

Create a new class called DictionaryBrowserViewController and give it these contents:

DictionaryBrowserViewController.h

#import 

@interface DictionaryBrowserViewController : UITableViewController {
	NSObject *sourceData;
}

- (id) initWithData:(NSObject *)object andTitle:(NSString *)text;

@end

@interface NSObject (NSObject_TypeCheckers)

- (BOOL) isNSDictionary;
- (BOOL) isNSArray;

@end

DictionaryBrowserViewController.m

#import "DictionaryBrowserViewController.h"

@implementation DictionaryBrowserViewController

- (id) initWithData:(NSObject *)object andTitle:(NSString *)text
{
	if (self == [super init])
	{
		sourceData = [object retain];
		self.navigationItem.title = text;
	}
	return self;
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

	if ([sourceData isNSDictionary]) return [(NSDictionary *)sourceData count];
	if ([sourceData isNSArray]) return (int)[(NSArray *)sourceData count];
	return 0;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{

    static NSString *CellIdentifier = @"any-cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
    }

	NSObject *obj;
	if ([sourceData isNSDictionary])
	{
		NSDictionary *dict = (NSDictionary *)sourceData;
		NSString *key = [[dict allKeys] objectAtIndex:indexPath.row];
		obj = [dict objectForKey:key];

		if ([obj isNSDictionary] || [obj isNSArray])
		{
			[cell setAccessoryType:UITableViewCellAccessoryDetailDisclosureButton];
			[cell.textLabel setText:key];
		}
		else
		{
			[cell setAccessoryType:UITableViewCellAccessoryNone];
			[cell.textLabel setText:[NSString stringWithFormat:@"%@ = %@", key, obj]];
		}

	} else if ([sourceData isNSArray]) {
		NSArray *ary = (NSArray *)sourceData;
		obj = [ary objectAtIndex:indexPath.row];

		if ([obj isNSDictionary] || [obj isNSArray])
		{
			[cell setAccessoryType:UITableViewCellAccessoryDetailDisclosureButton];
			[cell.textLabel setText:@"??"];
		}
		else
		{
			[cell setAccessoryType:UITableViewCellAccessoryNone];
			[cell.textLabel setText:[NSString stringWithFormat:@"%@",obj]];
		}
	} else { // we got a type of source data we don't understand
		[cell setAccessoryType:UITableViewCellAccessoryNone];
		[cell.textLabel setText:@"??"];
	}

    return cell;
}

 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
	NSString *key;
 	NSObject *obj;

	if ([sourceData isNSDictionary])
	{
		NSDictionary *dict = (NSDictionary *)sourceData;

		key = [[dict allKeys] objectAtIndex:indexPath.row];
		obj = [dict objectForKey:key];
	} else if ([sourceData isNSArray]) {
		key = @"??";
		obj = [(NSArray *)sourceData objectAtIndex:indexPath.row];
	} else {
		return;
	}

	if ([obj isNSArray] || [obj isNSDictionary]) {
		DictionaryBrowserViewController *next = [[DictionaryBrowserViewController alloc] initWithData:obj andTitle:key];
		[self.navigationController pushViewController:next animated:YES];
		[next release];
	}
 }

- (void)dealloc {
	[sourceData release];
    [super dealloc];
}

@end

@implementation NSObject (NSObject_TypeCheckers)

- (BOOL) isNSDictionary { return [self isKindOfClass:[NSDictionary class]]; }
- (BOOL) isNSArray		{ return [self isKindOfClass:[NSArray class]]; }

@end

Now go to your AppDelegate and give it these contents:
JsonExampleAppDelegate.h

@interface JsonExampleAppDelegate : NSObject  {
    UIWindow *window;
    UINavigationController *navController;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet UINavigationController *navController;

- (id) getData;

@end

JsonExampleAppDelegate.m

#import "JsonExampleAppDelegate.h"

@implementation JsonExampleAppDelegate

@synthesize window;
@synthesize navController;

- (void)dealloc {
	[navController release];
	[window release];
	[super dealloc];
}

- (void)applicationDidFinishLaunching:(UIApplication *)application {

	id data = [self getData];

	DictionaryBrowserViewController *controller = [[DictionaryBrowserViewController alloc] initWithData:data andTitle:@"Dictionary Browser"];
	navController = [[UINavigationController alloc] initWithRootViewController:controller];
	[navController.navigationBar setBarStyle:UIBarStyleBlackOpaque];

	[window addSubview:navController.view];
    [window makeKeyAndVisible];
}

- (id) getData
{
	NSMutableDictionary *subDict = [[NSMutableDictionary alloc] init];
	[subDict setObject:@"one" forKey:@"1"];
	[subDict setObject:@"two" forKey:@"2"];
	[subDict setObject:@"three" forKey:@"3"];

	NSMutableArray *subArray = [[NSMutableArray alloc] init];
	[subArray addObject:@"A"];
	[subArray addObject:@"B"];
	[subArray addObject:@"C"];

	NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
	[dict setObject:subArray forKey:@"letters"];
	[dict setObject:subDict forKey:@"numbers"];
	[dict setObject:@"bar" forKey:@"foo"];
	[dict setObject:@"baz" forKey:@"foobar"];

	return [dict autorelease];
}

@end

DictionaryBrowserViewController is just a simple UITableVewController that will, given an NSArray or NSDictionary as a data source, display the contents of the data source with disclosure indicators to allow navigation deeper into itself. The NSObject (NSObject_TypeCheckers) stuff is just there to add convenience methods to NSObject so we know if we’re dealing with an NSArray or an NSDictionary. The getData method of the AppDelegate just generates a junk dictionary containing some strings, and a sub-dictionary and a sub-array. Run the code, and it should look like this:

Picture 6.png

Now let’s get crankin’ on JSON. You can download the framework here. As of right now, the version number is 2.2.1.

The website indicates two possible ways to consume the code on an iPhone: Either link against a pre-compiled SDK, or include their source within your own project. While I’m personally more inclined to go the SDK route, I got strange compile-time errors in UIKit when I did. So, for this example, I’ll include the JSON code within my project.

Using Finder, open the JSON package you downloaded and find the JSON folder inside it. Drag the folder to your XCode window and drop it right below “JsonExample”. Indicate that you want to copy the items into the destination folder, and to recursively create groups.

Picture 3.png

Picture 5.png

A JSON group will be created for you, containing all the JSON source code. Collapse it to get it out of your way.

Let’s hard-code some JSON-formatted data just so we can see how it works. Import the JSON code in your AppDelegate.h

#import "JSON.h"

Now, change getData in AppDelegate.m to look like this:

- (id) getData
{
	NSString *jsonString = @"{ \"letters\":[\"A\",\"B\",\"C\"], \"numbers\":{\"1\":\"one\",\"2\":\"two\",\"3\":\"three\"}, \"foo\":\"bar\", \"foobar\":\"baz\" }";

	SBJSON *jsonParser = [SBJSON new];
	return [jsonParser objectWithString:jsonString error:NULL];
}

Run the code. The result should look exactly the same. That’s because the contents of jsonString is the JSON representation of the same NSDictionary we were creating through code before.

Picture 6.png

Once that works, let’s add the code to pull a JSON string from a URL. Add this method to your AppDelegate.m, and don’t forget to declare the method in your .h:

- (NSString *)getJsonString:(NSString *)url
{
	NSURLRequest *urlRequest = [NSURLRequest requestWithURL:  [NSURL URLWithString:url] ];

	NSURLResponse *response;
	NSError *error;

	NSData *returnData = [NSURLConnection sendSynchronousRequest:urlRequest
											   returningResponse:&response
														   error:&error];

	return [[NSString alloc] initWithData:returnData
								 encoding:NSUTF8StringEncoding];
}

Now change getData to look like this:

- (id) getData
{
	NSString *jsonString = [self getJsonString:@"http://www.bigsprocket.com/samples/json.php"];

	SBJSON *jsonParser = [SBJSON new];
	return [jsonParser objectWithString:jsonString error:NULL];
}

As you can see, we’re going to make an HTTP request to the url http://www.bigsprocket.com/samples/json.php. That page is just a PHP script written to generate the same JSON string we were using before. So, running it should produce exactly the same the same result…

Finally, we’re ready to try it out against a real stream. There are JSON services scattered all over the web. Let’s use one from Twitter. Change the URL in getData so the line reads:

NSString *jsonString = [self getJsonString: @"http://twitter.com/statuses/user_timeline/letstalkiphone.json?count=5"];

Now the results are decidedly different …

Picture 7.png

What you’re looking at are the last 5 tweets by @letsTalkiPhone. They show up as “?? (not named)” because the result from this particular service is an array of tweets that aren’t named. If you click through, though, you’ll see a dictionary representation of the tweet. The “text” entry in the dictionary contains the text of the tweet. So, you can think of this result as looking something like this:

	[NSArray
		[NSDictionary:   "text" = "some tweet" , ... ],
		[NSDictionary:   "text" = "another tweet", ... ],
		[NSDictionary:   "text" = "yet another", ... ],
		...
	]

With that in mind, let’s massage the data a little bit once we get it back… here’s another version of getData

- (id) getData
{
	NSString *jsonString = [self getJsonString:
							@"http://twitter.com/statuses/user_timeline/letstalkiphone.json?count=5"];

	SBJSON *jsonParser = [SBJSON new];

	NSArray *originalData = [jsonParser objectWithString:jsonString error:NULL];

	NSMutableArray *tweetTextOnly = [[NSMutableArray alloc] initWithCapacity:5];
	for (int i = 0; i < 5; i++)
	{
		NSDictionary *internalDict = [originalData objectAtIndex:i];
		[tweetTextOnly addObject:[internalDict valueForKey:@"text"]];
	}

	return tweetTextOnly;
}

Which, at the time of this writing, looks like this ...

Picture 8.png

Voila! Here's our final code. JsonExample.zip

Obviously, the code isn't production quality, needs error handling in case of no network connection, etc, but it'll get you started. There's a lot more to explore about JSON and the framework, and I plan to investigate it a lot more. If you want to know more, here are some websites to check out.

Are you playing with JSON services? Let me know in the comments.

Back Top

Responses to “Using JSON WebServices”

  1. No comments yet.

Leave a Reply

You must be logged in to post a comment.