I got tired of stringing together objectAtIndex: and objectForKey: and hoping nothing fails along the way. I parse a lot of JSON from sources like the Google Directions API and it was cluttering my code and hard to read. In this example I am parsing the JSON returned by this call:
I just learned Objective-C and how to program (beyond Matlab) and want some feedback on the best practices for what I am doing.
Usage
I call the class method below and pass in a dictionary along with a cascading series of keys for a dictionary and indexes for arrays. I defined a couple of macros to reduce typing.
Usage code
NSTimeInterval flightTime = [(NSNumber *)[RDUtilities objectFromNestedJSON:responseDictionary usingCascadedKeys:RDJSONKey(@"routes"),RDJSONIndex(0), RDJSONKey(@"legs"), RDJSONIndex(0), RDJSONKey(@"duration"), RDJSONKey(@"value")] doubleValue];
Implementation Code
#define RDJSONKey(x) @{@"dKey":x}
#define RDJSONIndex(x) @{@"aIndex":@x}
@implementation RDUtilities
+(id) objectFromNestedJSON:(id)JSONObject usingCascadedKeys:(NSDictionary*)firstArg,...
{
NSMutableArray *keyDexList = [[NSMutableArray alloc] init];
NSArray *subArray = nil;
NSDictionary *subDictionary = nil;
id subObject = nil;
va_list args;
va_start(args, firstArg);
// Figure out the type of JSONObject.
if ([JSONObject isKindOfClass:[NSDictionary class]])
{
subDictionary = JSONObject;
} else if ([JSONObject isKindOfClass:[NSArray class]])
{
subArray = JSONObject;
} else {
return nil;
}
// Iterate through the list of arguments.
for (NSDictionary *arg = firstArg; arg != nil; arg = va_arg(args, NSDictionary *))
{
if ( [[arg allKeys] containsObject:@"dKey"] || [[arg allKeys] containsObject:@"aIndex"])
{
[keyDexList addObject:arg];
}
else {
NSLog(@"Invalid input types");
return nil;
}
}
// Look at the keyDex and pull out the next subObject from the current correct subObject.
NSArray *allButLastKeyDex = [keyDexList objectsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [keyDexList count]-1)]];
for (NSDictionary *keyDex in allButLastKeyDex)
{
if (subDictionary != nil && [[keyDex allKeys] containsObject:@"dKey"])
{
// Get the sub object that this key references.
subObject = [subDictionary objectForKey:[keyDex objectForKey:@"dKey"]];
// Figure out what we got.
if ([subObject isKindOfClass:[NSDictionary class]])
{
subDictionary = subObject;
subArray = nil; // We can be sure we don't try to use this incorrectly now.
}
else if ([subObject isKindOfClass:[NSArray class]])
{
subArray = subObject;
subDictionary = nil; // We can be sure we don't try to use this incorrectly now.
}
// Unneeded due to containsObject constraint.
continue;
}
if (subArray != nil && [[keyDex allKeys] containsObject:@"aIndex"])
{
// Get the sub object that this key references.
subObject = [subArray objectAtIndex:[(NSNumber *)[keyDex objectForKey:@"aIndex"] integerValue]];
// Figure out what we got.
if ([subObject isKindOfClass:[NSDictionary class]])
{
subDictionary = subObject;
subArray = nil; // Safer until we verify logic.
}
else if ([subObject isKindOfClass:[NSArray class]])
{
subArray = subObject;
subDictionary = nil; // Safer until we verify logic.
}
// Unneeded due to containsObject constraint.
continue;
}
}
// Get the last key or index.
NSDictionary *finalKeyDex = [keyDexList lastObject];
// Pull out the final value and return it.
if ([[finalKeyDex allKeys] containsObject:@"dKey"])
{
return [subDictionary objectForKey:[finalKeyDex objectForKey:@"dKey"]];
}
else if ([[finalKeyDex allKeys] containsObject:@"aIndex"])
{
return [subArray objectAtIndex:[(NSNumber *)[finalKeyDex valueForKey:@"aIndex"] integerValue]];
}
else {
return nil;
}
}
@end