Tell me more ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

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:

http://maps.googleapis.com/maps/api/directions/json?origin=415OliveStMenloPark&destination=425ShermanAvePaloAlto&sensor=false&mode=driving

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
share|improve this question
add comment (requires an account with 50 reputation)

Know someone who can answer? Share a link to this question via email, Google+, Twitter, or Facebook.

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Browse other questions tagged or ask your own question.