7

Is there a way via dot notation to access the values of keys in an NSDictionary like this?

NSDictionary *returnVal = [NSDictionary dictionaryWithObjectsAndKeys:@"Saturn", @"name", @"Gas Giant", @"type", nil];
NSLog(@"VALUE: %@", [returnVal valueForKey:@"name"]); // This is how I am doing it now.
fuzzygoat
  • 26,573
  • 48
  • 165
  • 294

9 Answers9

6

There is no dot syntax for NSDictionary, but should consider using objectForKey: instead of valueForKey:

Difference between objectForKey and valueForKey?

Community
  • 1
  • 1
kevboh
  • 5,207
  • 5
  • 38
  • 54
5

Not really, no.

The dot notation is a shorthand way of calling a method with that selector name. In other words, this...

NSLog(@"Hello, %@", foo.bar.name);

...is the same as this...

NSLog(@"Hello, %@", [[foo bar] name]);

When I say "same", I mean they are compiled down to the same code. It's just syntactic sugar.

A plain NSDictionary won't act that way. You could sort of fake it with Key Value Coding, which lets you call valueForKeyPath to get properties like this:

NSLog(@"Hello, %@", [foo valueForKeyPath:@"bar.name"]);

If you really wanted to be able to write foo.bar.name in your code, however, you'd have to make a custom class that overrides forwardInvocation:; this lets you catch an unknown message to an object and do something else with it besides throw an error. In this case, you could change the unknown selector to a lookup on an NSDictionary instance it contains.

But even if you did that, the compiler would probably still generate warnings unless you made header files that declared those property names to exist.

benzado
  • 82,288
  • 22
  • 110
  • 138
3

I agree with most of the answers that NSDictionary should be accessed with objectForKey: or similar methods. However it is possible to allow for dot notation access to a NSDictionary, and for learning purposes this might be interesting for someone. Also when for example your are retrieving large JSON dictionaries via AFNetworking, this method can ease the access and readability of your code.

This is my solution:

DictionaryProperties.h: (class wrapping the NSDictionary for property access)

@interface DictionaryProperties : NSObject{
    NSMutableDictionary* _backingDict;
}

@property (nonatomic, strong) NSMutableDictionary* backingDict;

+ (DictionaryProperties*) allocWithDictionary:(NSDictionary*)dict;

@end

DictionaryProperties.m:

#import "DictionaryProperties.h"

@implementation DictionaryProperties

@synthesize backingDict = _backingDict;

- (id) initWithDictionary:(NSDictionary*)dict {
    if (self) {
        if ([dict isKindOfClass:[NSMutableDictionary class]]) {
            self.backingDict = (id)dict;
        } else {
            self.backingDict = [[NSMutableDictionary alloc] initWithDictionary:dict];
        }
    }
    return self;
}
+ (DictionaryProperties*) allocWithDictionary:(NSDictionary*)dict {
    return [[DictionaryProperties alloc] initWithDictionary:dict];
}


- (void)forwardInvocation:(NSInvocation *)invocation
{
    NSString* key = NSStringFromSelector(invocation.selector);
    invocation.selector = @selector(objectForKey:);
    [invocation setArgument:&key atIndex:2];

    if ([self.backingDict objectForKey:key]) {
        [invocation invokeWithTarget:self.backingDict];
    } else {
        [self doesNotRecognizeSelector:invocation.selector];
    }
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    return [self.backingDict methodSignatureForSelector:@selector(objectForKey:)];

}
@end

ExampleDictContent.h: (class declaring what is inside the dictionary)

#import "DictionaryProperties.h"

@interface ExampleDictContent : DictionaryProperties

@property (strong, nonatomic) NSString* someData;
@property (strong, nonatomic) NSString* someOtherData;

@end

@implementation ExampleDictContent
@end

Usage: (simple declaration of a dictionary, allocation of wrapper and property access)

#import "ExampleDictContent.h"

NSDictionary* d = [NSDictionary dictionaryWithObjects:NSArray arrayWithObjects:@"someData content", @"someOtherData content", nil
                                              forKeys:NSArray arrayWithObjects:@"someData", @"someOtherData", nil];

ExampleDictContent* dictWProps = [ExampleDictContent allocWithDictionary:d];

NSLog(dictWProps.someData);
NSLog(dictWProps.someData);

This will print:

someData content
someOtherData content

So basically DictionaryProperties works as a facade for accessing the NSDictionary. It uses forwardInvocation to convert a get-property method call into a getObjectForKey: call on the dictionary. What I like about it, is that it allows for autocompletion on the dictionary, and also allows me to explicitly declare what keys I want to access (in the ExampleDictContent.h file). Note that this solution does not allow for write access to the properties, but that can be added as shown in the link below.

This solution has partly been inspired by karstenlitsche's solution. The main difference is that this solution is based on sub-classing instead of categories.

KlausCPH
  • 1,816
  • 11
  • 14
1

No, you are doing it the correct way. In the iOS world, often the correct way is the only way. :)

If you really want dot notation (and other nice things you get with typed objects), you're going to have to stuff the dictionary representation into an object. Most commonly my interface will look like:

@interface FooBar : NSObject {
    NSString *someData;
    int someNumber;
}

@property (nonatomic, copy) NSString *someData;
@property (nonatomic, assign) int someNumber;

+ (FooBar *)FooBarFromDictionary:(NSDictionary *)dataDict;

@end

The implementation should be clear. Then you can

FooBar *fb = [FooBar FooBarFromDictionary:data];
NSLog(@"fb.someData = %@", fb.someData);
bensnider
  • 3,742
  • 1
  • 24
  • 25
1

No, I don't think so.

From the reference manual.

Accessing Keys and Values
– allKeys
– allKeysForObject:
– allValues
– getObjects:andKeys:
– objectForKey:
– objectsForKeys:notFoundMarker:
– valueForKey:

That's listed as the only way to access the keys and the values. So you are doing it alright.

You would be able to access it if the keys were a public property and it was readable.

El Developer
  • 3,345
  • 1
  • 21
  • 40
1

The way that you have mentioned for accessing element of dictionary is ideal way(using keys). If you want to do something else, might be you can use-

NSArray *allValues = [returnVal allValues];

Now using this array as well you can perform tasks. And if you want something specific then mention that, might be for that there can be some other way.

Also as NSDictionary class won't have any property defined, so dot notation is directly not possible.

rishi
  • 11,779
  • 4
  • 40
  • 59
0

The answer's still no, but you can use the shorthand

myDictionary[@"key"]

instead of

[myDictionary objectForKey:@"key"]
Al.
  • 173
  • 1
  • 11
0

In Swift, there is a solution that may not seem very elegant but does the trick.

It will require a typeAlias for each specific type of Dictionary and also an extension with variables (with getter/setter) for each of the expected keys in your dictionary. Not a good practice at all

It may be easier wrap your dict object in an object (class/struct) with the same treatment.

typealias MyDict = [String:AnyObject]
extension MyDict {
    var key: AnyObject? {
        get { return self["key"] }
        set { self["key"] = newValue }
    }
}


// Usage
var myDict = MyDict()

// Get the value
myDict["key"] = "value1" as AnyObject
if let str = myDict.key {
    print(str)  // prints "value1"
}

// Set the value
myDict.key = "value2" as AnyObject
if let str = myDict["key"] {
    print(str)  // prints "value2"
}
eharo2
  • 2,553
  • 1
  • 29
  • 39
0

Technically, you can do something like this:

typedef id (^valueBlock)(id);
@interface NSDictionary(dotNotationAddons)

@property(nonatomic, readonly) valueBlock value;

@end

@implementation NSDictionary(dotNotationAddons)

-(valueBlock) value
{
    return [[^(id key) {
        return [self objectForKey:key];  
    } copy] autorelease];
}

@end

int main (int argc, const char * argv[])
{
    @autoreleasepool {
        NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:@"1", @"One", @"2", @"Two", @"3", @"Three", @"4", @"Four", nil];

        id value = dictionary.value(@"One");

        NSLog(@"%@", value);
    }


    return 0;
}

I don't know if that is what you were looking for, but I hope it helps!

Richard J. Ross III
  • 55,009
  • 24
  • 135
  • 201
  • I don't understand why someone would honestly down vote this. It answers what the OP asked, how can I access a dictionary with dot notation. This allows for it. – Richard J. Ross III Feb 13 '12 at 17:09
  • 2
    This is interesting but how does it related to @fuzzygoat's question? – benzado Feb 13 '12 at 17:10
  • @benzado because it lets him use a NSDictionary with dot-notation, which is what he wanted? – Richard J. Ross III Feb 13 '12 at 17:10
  • 4
    Yes, you are accessing a dictionary and you are using dot notation, but you aren't really using dot notation to access the keys in a dictionary. – benzado Feb 13 '12 at 17:11
  • -1 This code does some obfuscated mumble-jumble to perform a simple task. It does not help an Objectve-C beginner to access values from a dictionary. – Nikolai Ruhe Feb 13 '12 at 17:22
  • 1
    Thank you for taking the time Richard. I think the answer is there is not a simple way (i.e. dictionary.key) so for now I am going to swap to using objectForKey. Again much appreciated ... – fuzzygoat Feb 13 '12 at 17:23