Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Recursively traverse NSDictionary of unknown structure

Has anyone done a recursive ordered traversal of an NSDictionary of unknown structure? I'd like to take any NSDictionary and process each level in hierarchical order.

1) This data is coming from validated JSON. Is it safe to say that the NSDictionary created from a framework such as SBJSON (JSON Framework) would only result in a combination of nested dictionaries, arrays, and arbitrary leafs?

2) How can a generic traversal be done using fast enumeration that works for both arrays and dictionaries? With the code below, once I get to a dictionary within an array, it stops traversing. However, if I continue the recursion in the array condition (to check for dictionaries within arrays), it barfs on the next iteration of id value = [dict valueForKey:key]; with a -[__NSCFDictionary length]: unrecognized selector sent to instance SIGABRT. I don't know why this would be a problem, because I already got past that line with a top-level dictionary (where the array of sub-level dictionaries were found).

-(void)processParsedObject:(id)dict counter:(int)i parent:(NSString *)parent
{
    for (id key in dict) {
        id value = [dict valueForKey:key];
        NSLog(@"%i : %@ : %@ -> %@", i, [value class], parent, key);

        if ([value isKindOfClass:[NSDictionary class]])
        {
            i++;
            NSDictionary* newDict = (NSDictionary*)value;
            [self processParsedObject:newDict counter:i parent:(NSString*)key];
            i--;
        }
        else if ([value isKindOfClass:[NSArray class]])
        {
            for (id obj in value) {
                NSLog(@"Obj Type: %@", [obj class]);
            }
        }
    }
}

Many thanks

like image 867
Old McStopher Avatar asked Feb 08 '12 00:02

Old McStopher


People also ask

How do you traverse a matrix recursively?

Approach: Below are the steps to traverse the Matrix recursively: Base Case: If the current row or column exceeds the size N or M, respectively, the recursion is stopped. If the current column exceeds the total number of columns M, then the next row is started. if (current_col >= M) This row ends.

What problems can be solved by using a recursive function?

Even if a compiler supports loops, some problems are easier to solve with a recursive function. A good example is tree traversal. I often write recursive functions to find every property of any JSON object, or to search every file in a folder that may have an infinite number of nested subfolders.

How to recursively iterate a nested Python dictionary?

How to recursively iterate a nested Python dictionary? Given below is a nested directory object Following recursive function is called repetitively if the value component of each item in directory is a directory itself. When the initial dictionary object is passed to this function, all the key-value pairs are traversed.

What is the use of traverse method in IEnumerable?

var traverse = Extensions.Y<IEnumerable<Item>, IEnumerable<Item>> (...) Traverse is an extension method for IEnumerable<Item> which takes a predicate of type Func<Item, bool> as a parameter. This parameter will contain the lambda expression encapsulating the selection logic for the items in the hierarchy.


2 Answers

I've done something similar where I would traverse a JSON structured object coming from a web service and convert each element into a mutable version.

- (void)processParsedObject:(id)object
{
    [self processParsedObject:object depth:0 parent:nil];
}

- (void)processParsedObject:(id)object depth:(int)depth parent:(id)parent
{      
    if ([object isKindOfClass:[NSDictionary class]])
    {
        for (NSString* key in [object allKeys])
        {
            id child = [object objectForKey:key];
            [self processParsedObject:child depth:(depth + 1) parent:object];
        }
    }
    else if ([object isKindOfClass:[NSArray class]])
    {
        for (id child in object)
        {
            [self processParsedObject:child depth:(depth + 1) parent:object];
        }   
    }
    else
    {
        // This object is not a container you might be interested in it's value
        NSLog(@"Node: %@  depth: %d", [object description], depth);
    }
}
like image 56
Joel Kravets Avatar answered Oct 26 '22 20:10

Joel Kravets


I needed to know the key names for the values so I rewrote what Joel had and came up with this:

- (void)enumerateJSONToFindKeys:(id)object forKeyNamed:(NSString *)keyName
{
    if ([object isKindOfClass:[NSDictionary class]])
    {
        // If it's a dictionary, enumerate it and pass in each key value to check
        [object enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) {
            [self enumerateJSONToFindKeys:value forKeyNamed:key];
        }];
    }
    else if ([object isKindOfClass:[NSArray class]])
    {
        // If it's an array, pass in the objects of the array to check
        [object enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
            [self enumerateJSONToFindKeys:obj forKeyNamed:nil];
        }];
    }
    else
    {
        // If we got here (i.e. it's not a dictionary or array) so its a key/value that we needed 
        NSLog(@"We found key %@ with value %@", keyName, object);
    }
}

And then you'd call it like:

[self enumerateJSONToFindKeys:JSON forKeyNamed:nil];

Alternatively, if you wanted to make a given key path mutable (so you could write back to it using setValue:forKeyPath:), you could do something like below:

- (void)makeDictionariesMutableForKeyPath:(NSString *)keyPath {
    NSArray *keys = [keyPath componentsSeparatedByString:@"."];

    NSString *currentKeyPath = nil;
    for (NSString *key in keys) {
        if (currentKeyPath) {
            NSString *nextKeyPathAddition = [NSString stringWithFormat:@".%@", key];
            currentKeyPath = [currentKeyPath stringByAppendingString:nextKeyPathAddition];
        } else {
            currentKeyPath = key;
        }

        id value = [self.mutableDictionary valueForKeyPath:currentKeyPath];
        if ([value isKindOfClass:NSDictionary.class]) {
            NSMutableDictionary *mutableCopy = [(NSDictionary *)value mutableCopy];
            [self.mutableDictionary setValue:mutableCopy forKeyPath:currentKeyPath];
        }
    }

}
like image 28
iwasrobbed Avatar answered Oct 26 '22 20:10

iwasrobbed