Getting Started with JSON in iOS5

Update: This post has been cross-posted on my employers blog.

JSON has taken the data-interchange world by storm with its’ lightweight, easy to understand format. The explosion of mobile apps, and their consumption of network based data, helped fuel JSON’s growth. CapTech’s (my employer) recommended approach for web services utilizes JSON as the interchange format.

While JSON has been around for several years – RFC4627 was published in July of 2006 – working with JSON in an iOS project required that you download one of the many frameworks and integrate it into your project. My personal choice has been SBJSON – https://github.com/stig/json-framework.

However, with the release of iOS5, Apple has finally included native support for reading and writing JSON with the class NSJSONSerialization.

Why might you consider using the native JSON API? First and foremost, Apple delivered, Apple supported. While support for most third-party libraries is good, you can’t beat updated, built-in, backwards compatible support (for future versions of the OS) on day one of a new SDK.

One unfortunate con, use of NSJSONSerialization is limited to devices running iOS5. While iOS5 adoption is moving at a great pace, this still vastly limits your install base. In the case of some enterprises with field devices deployed with previous versions of the OS, this may not be an option for quite some time.

It’s important to note, calling any of the NSJSONSerialization methods on a device running an older version of iOS will not cause the application to crash. It simply won’t work and will continue on as if the call was never made. Obviously, there are other impacts of this, but this may fit some needs.

Now, let’s cover implementing the 4 read/write methods that Apple has exposed. As you can see below, using NSJSONSerialization in your projects is incredibly easy, typically requiring just a few lines of code. NSJSONSerialization supports the reading and writing of JSON data via static data or an instance of NS*Stream. The NS*Stream examples below are rudimentary, but they convey what is needed in order for NSJSONSerialization to do its’ job.

Parsing JSON Data

The parsing methods accept an options parameter which allow you to pass different instructions to manipulate what type of foundation object you get back.

  • NSJSONReadingAllowFragments: instructs the parser to allow top-level objects that are neither an NSArray nor NSDictionary
  • NSJSONReadingMutableContainers: instructs the parser to generate NSMutableArray and NSMutableDictionary objects
  • NSJSONReadingMutableLeaves: instructs the parser to generate NSMutableString objects

Method: JSONObjectWithData

This method allows us to create a JSON object from an instance of NSData.  In this example, we’ll assume that an authentication request was successful (see below).  The first portion of the code will be to generate the mock-JSON response we received from the service.  The code will parse that response and create a foundation object.  From there, we’ll check if the object is an NSDictionary (there are a couple ways of doing this) and, if so, log the returned message and my token.

    NSDictionary *data = [NSDictionary dictionaryWithObjectsAndKeys:@"Authentication Successful", @"auth_response",@"abc123", @"auth_token", nil];
    NSError *writeError = nil;
    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:data options:NSJSONWritingPrettyPrinted error:&writeError];
 
    // parse the JSON data into what is ultimately an NSDictionary
    NSError *parseError = nil;
    id jsonObject = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingAllowFragments error:&parseError];
 
    // test that the object we parsed is a dictionary - perhaps you would test for something different
    if ([jsonObject respondsToSelector:@selector(objectForKey:)]) {
        NSLog(@"Response: %@", [jsonObject objectForKey:@"auth_response"]);
        NSLog(@"Token: %@", [jsonObject objectForKey:@"auth_token"]);
    }

Method: JSONObjectWithStream

This method allows us to create a JSON object from an input stream. When and why to use NSInputStream is out of scope for this post. However, if you have a large chunk of JSON data you want to parse, it may be one option to explore.

In this example, we create an instance of NSData that contains JSON formatted tweet information. While not the recommended network communication approach, this allows us to quickly generate an NSData object for our stream.  Once the stream is up, we’ll create a foundation object from the JSON data, test that it is an NSDictionary (which we expect given the source data we’re retrieving), and then log the tweet to the console.

It’s important to note that an NS*Stream must be created AND configured in order for NSJSONSerialization to work properly. Once parsed, we simply log each of the tweets to the console.

    NSData *tweets = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://www.search.twitter.com/search.json?q=from:nathanhjones"]];
    NSInputStream *twitterStream = [[NSInputStream alloc] initWithData:tweets];
    [twitterStream open];
 
    if (twitterStream) {
        NSError *parseError = nil;
        id jsonObject = [NSJSONSerialization JSONObjectWithStream:twitterStream options:NSJSONReadingAllowFragments error:&parseError];        
        if ([jsonObject respondsToSelector:@selector(objectForKey:)]) {
            for (NSDictionary *tweet in [jsonObject objectForKey:@"results"]) {
                NSLog(@"Tweet: %@", [tweet objectForKey:@"text"]);
            }
        }
    } else {
        NSLog(@"Failed to open stream.");
    }

Writing JSON Data

The JSON writing methods also have an options parameter, but it’s currently limited to a single option.

  • NSJSONWritingPrettyPrinted: instructs the writer to generate JSON with whitespace designed to make output more readable. If this option is not used, the most compact JSON possible will be generated.

Method: dataWithJSONObject

This method allows us to create a JSON formatted NSData object from a foundation object – typically NSDictionary or NSArray.

In this example, we’ll build a dictionary of authentication credentials and then convert that to JSON as if we were posting it to an authentication service.

    NSDictionary *data = [NSDictionary dictionaryWithObjectsAndKeys:@"test@test.com", @"user",@"mypass", @"pass", nil];
 
    NSError *error = nil;
    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:data options:NSJSONWritingPrettyPrinted error:&writeError];
 
    NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
    NSLog(@"JSON Output: %@", jsonString);

Method: writeJSONObject:toStream

This method allows us to write our JSON directly to an output steam. Again, when to use NSOutputStream is out of scope for this post, but it is an option. For this example, we’ll build an array of all tweet details for my last 3 tweets. We’ll then scrub that data down to 3 key pieces of information for each tweet, which we will then write to an output stream. In this case, we’ll simply write the contents to a local file.

// get tweet data and convert to foundation object
    NSData *tweetData = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://www.search.twitter.com/search.json?q=from:nathanhjones"]];
    NSError *parseError = nil;
    id jsonObject = [NSJSONSerialization JSONObjectWithData:tweetData options:NSJSONReadingAllowFragments error:&parseError];
 
    // initialize an array to hold our reformatted tweets
    NSMutableArray *tweets = [[NSMutableArray alloc] init];
 
    // check that we received a dictionary - there are 'header' objects sent by twitter
    if ([jsonObject respondsToSelector:@selector(objectForKey:)]) {
        // loop through all the actual tweets
        for (NSDictionary *tweet in [jsonObject objectForKey:@"results"]) {   
            // create a dictionary of minimal tweet data and add to output
            NSDictionary *scrubbedTweet = [NSDictionary dictionaryWithObjectsAndKeys:
                                           [tweet objectForKey:@"id"], @"id",
                                           [tweet objectForKey:@"created_at"], @"created_at",
                                           [tweet objectForKey:@"text"], @"text",
                                           nil];
 
            [tweets addObject:scrubbedTweet];        
        }
    }
 
    // initialize and open the stream
    NSString *documents = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    NSString *path = [NSString stringWithFormat:@"%@/tweets.json", documents];
    NSOutputStream *stream = [[NSOutputStream alloc] initToFileAtPath:path append:YES];
    [stream open];
 
    // write JSON representation of our tweet array to file
    NSError *writeError = nil;
    NSInteger bytesWritten = [NSJSONSerialization writeJSONObject:tweets toStream:stream options:NSJSONWritingPrettyPrinted error:&writeError];
 
    if (bytesWritten <= 0) {
        NSLog(@"Error writing JSON Data");
    }

One additional method I find particularly interesting, isValidJSONObject.  An easy way to validate that the object you’ve created can be converted to JSON data.  I could be wrong, but I don’t recall any of the third-party libraries I’ve used having such a method.

If you’ve got questions, I’m @nathanhjones.

Did you like this? Share it:

Tags: , , ,

Leave a Reply