Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting array elements with valueForKeyPath

Is there any way to access an NSArray element with valueForKeyPath? Google's reverse geocoder service, for example, returns a very complex data structure. If I want to get the city, right now I have to break it into two calls, like this:

NSDictionary *address = [NSString stringWithString:[[[dictionary objectForKey:@"Placemark"] objectAtIndex:0] objectForKey:@"address"]];
NSLog(@"%@", [address valueForKeyPath:@"AddressDetails.Country.AdministrativeArea.SubAdministrativeArea.Locality.LocalityName"]);

Just wondering if there's a way to shoehorn the objectAtIndex: call into the valueForKeyPath string. I tried a javascript-esque formulation like @"Placemark[0].address" but no dice.

like image 879
jsd Avatar asked Sep 22 '09 16:09

jsd


3 Answers

Unfortunately, no. The full documentation for what's allowed using Key-Value Coding is here. There are not, to my knowledge, any operators that allow you to grab a particular array or set object.

like image 143
Alex Avatar answered Oct 10 '22 18:10

Alex


Here's a category I just wrote for NSObject that can handle array indexes so you can access a nested object like this: "person.friends[0].name"

@interface NSObject (ValueForKeyPathWithIndexes)
   -(id)valueForKeyPathWithIndexes:(NSString*)fullPath;
@end


#import "NSObject+ValueForKeyPathWithIndexes.h"    
@implementation NSObject (ValueForKeyPathWithIndexes)

-(id)valueForKeyPathWithIndexes:(NSString*)fullPath
{
    NSRange testrange = [fullPath rangeOfString:@"["];
    if (testrange.location == NSNotFound)
        return [self valueForKeyPath:fullPath];

    NSArray* parts = [fullPath componentsSeparatedByString:@"."];
    id currentObj = self;
    for (NSString* part in parts)
    {
        NSRange range1 = [part rangeOfString:@"["];
        if (range1.location == NSNotFound)          
        {
            currentObj = [currentObj valueForKey:part];
        }
        else
        {
            NSString* arrayKey = [part substringToIndex:range1.location];
            int index = [[[part substringToIndex:part.length-1] substringFromIndex:range1.location+1] intValue];
            currentObj = [[currentObj valueForKey:arrayKey] objectAtIndex:index];
        }
    }
    return currentObj;
}
@end

Use it like so

NSString* personsFriendsName = [obj valueForKeyPathsWithIndexes:@"me.friends[0].name"];

There's no error checking, so it's prone to breaking but you get the idea.

like image 19
psy Avatar answered Oct 10 '22 17:10

psy


You can intercept the keypath in the object holding the NSArray.

In your case the keypath would become Placemark0.address... Override valueForUndefinedKey; look for the index in the keypath; something like this:

-(id)valueForUndefinedKey:(NSString *)key
{
    // Handle paths like Placemark0, Placemark1, ...
    if ([key hasPrefix:@"Placemark"])
    {
        // Caller wants to access the Placemark array.
        // Find the array index they're after.
        NSString *indexString = [key stringByReplacingOccurrencesOfString:@"Placemark" withString:@""];
        NSInteger index = [indexString integerValue];

        // Return array element.
        if (index < self.placemarks.count)
            return self.placemarks[index];
    }

    return [super valueForUndefinedKey:key];
}

This works really well for model frameworks e.g. Mantle.

like image 3
Graham Perks Avatar answered Oct 10 '22 16:10

Graham Perks