Skip to content

Instantly share code, notes, and snippets.

@valexa
Created April 18, 2011 13:42
Show Gist options
  • Save valexa/925355 to your computer and use it in GitHub Desktop.
Save valexa/925355 to your computer and use it in GitHub Desktop.
a method for changing objects inside deeply nested dictionaries
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
//create a example nested dictionary with just 4 levels
NSMutableDictionary *parent = [[[NSMutableDictionary alloc] init] autorelease];
NSDictionary *thirdChild = [NSDictionary dictionaryWithObjectsAndKeys:@"bi",@"l4",@"nar",@"l4_", nil];
NSDictionary *secondChild = [NSDictionary dictionaryWithObjectsAndKeys:thirdChild,@"l3",@"bar",@"l3_", nil];
NSDictionary *firstChild = [NSDictionary dictionaryWithObjectsAndKeys:secondChild,@"l2",secondChild,@"l2_", nil];
[parent setObject:firstChild forKey:@"l1"];
[parent setObject:firstChild forKey:@"l1_"];
//NSLog(@"%@",[parent description]);
//NSLog(@"Was: %@",[[[parent objectForKey:@"l1_"] objectForKey:@"l2_"] objectForKey:@"l3_"]);
NSLog(@"Was: %@",[parent valueForKeyPath:@"l1_.l2_.l3_"]);
//let's change the value of l1_/l2_/l3_ to baz, the goal is a one line call to a method with minimal overhead
//manual method for reference, gets out of hand fast when dealing with deeply nested structures and has no type checking
if (1 == 2) {
NSMutableDictionary *firstChild = [[parent objectForKey:@"l1_"] mutableCopy];
NSMutableDictionary *secondChild = [[firstChild objectForKey:@"l2_"] mutableCopy];
[secondChild setObject:@"baz" forKey:@"l3_"];
[firstChild setObject:secondChild forKey:@"l2_"];
[secondChild release];
[parent setObject:firstChild forKey:@"l1_"];
[firstChild release];
}
//proposed method
NSDictionary *changed_parent = [self editNestedDict:parent setObject:@"baz" forKeyHierarchy:[NSArray arrayWithObjects:@"l1_",@"l2_",@"l3_", nil]];
//NSLog(@"%@",[changed_parent description]);
//NSLog(@"Is: %@",[[[changed_parent objectForKey:@"l1_"] objectForKey:@"l2_"] objectForKey:@"l3_"]);
NSLog(@"Is: %@",[changed_parent valueForKeyPath:@"l1_.l2_.l3_"]);
}
-(NSDictionary*)editNestedDict:(NSDictionary*)dict setObject:(id)object forKeyHierarchy:(NSArray*)hierarchy{
if (dict == nil) return dict;
if (![dict isKindOfClass:[NSDictionary class]]) return dict;
NSMutableDictionary *parent = [[dict mutableCopy] autorelease];
//drill down mutating each dict along the way
NSMutableArray *structure = [NSMutableArray arrayWithCapacity:1];
NSMutableDictionary *prev = parent;
for (id key in hierarchy) {
if (key != [hierarchy lastObject]) {
prev = [[[prev objectForKey:key] mutableCopy] autorelease];
if (![prev isKindOfClass:[NSDictionary class]]) return dict;
[structure addObject:prev];
NSLog(@"loading %@",key);
}else{
NSLog(@"changing %@",key);
}
}
//do the change
[[structure lastObject] setObject:object forKey:[hierarchy lastObject]];
//drill back up saving the changes each step along the way
for (int c = [structure count]-1; c >= 0; c--) {
if (c == 0) {
[parent setObject:[structure objectAtIndex:c] forKey:[hierarchy objectAtIndex:c]];
}else{
[[structure objectAtIndex:c-1] setObject:[structure objectAtIndex:c] forKey:[hierarchy objectAtIndex:c]];
}
NSLog(@"saving %@",[hierarchy objectAtIndex:c]);
}
return parent;
}
@mikeash
Copy link

mikeash commented Apr 18, 2011

A recursive version would look something like this:

-(NSDictionary*)editNestedDict:(NSDictionary*)dict setObject:(id)object forKeyHierarchy:(NSArray*)hierarchy
{
    NSMutableDictionary *newDict = [NSMutableDictionary dictionaryWithDictionary: dict];

    if([hierarchy count] > 1)
    {
        NSString *key = [hierarchy objectAtIndex: 0];
        NSString *remainder = [hierarchy subArrayWithRange: NSMakeRange(1, [hierarchy count] - 1)];

        NSDictionary *subDict = [newDict objectForKey: key];
        subDict = [self editNestedDict: subDict setObject: object forKeyHierarchy: remainder];
        [newDict setObject: subDict forKey: key];
    }
    else
        [newDict setObject: object forKey: [hierarchy lastObject]];

    return newDict;
}

@hajikelist
Copy link

NSString *remainder = [hierarchy subArrayWithRange: NSMakeRange(1, [hierarchy count] - 1)];
should probably be:
NSArray *remainder = [hierarchy subArrayWithRange: NSMakeRange(1, [hierarchy count] - 1)];

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment