8

I have a json string

{"name":"test","bar":{"name":"testBar"}}

In objective c I have an object

@interface Foo : NSObject {
}
@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) Bar * bar;
@end

And I just synthesize those properties. And I have a child object with synthesized properties.

@interface Bar : NSObject {
}
@property (nonatomic, retain) NSString * name;
@end

Then here is the code where I'm trying to get into the Foo object where response is the json string above:

    SBJsonParser *json = [[SBJsonParser new] autorelease];
    parsedResponse = [json objectWithString:response error:&error];
    Foo * obj = [[Foo new] autorelease];
    [obj setValuesForKeysWithDictionary:parsedResponse];
    NSLog(@"bar name %@", obj.bar.name);

This throws an exception on the NSLog statement of:

-[__NSCFDictionary name]: unrecognized selector sent to instance 0x692ed70'

But if I change the code to it works:

NSLog(@"bar name %@", [obj.bar valueForKey:@"name"]);

I'm confused at why I can't do the first example, or am I doing something wrong?

John Calsbeek
  • 35,947
  • 7
  • 94
  • 101
Tavis Bones
  • 255
  • 4
  • 12

2 Answers2

7

Have you tried this?

// Foo class

-(void)setBar:(id)bar
{
    if ([bar class] == [NSDictionary class]) {
        _bar = [Bar new];
        [_bar setValuesForKeysWithDictionary:bar];
    }
    else
    {
        _bar = bar;
    }
}
Tuan Nguyen
  • 71
  • 1
  • 2
6

-setValuesForKeysWithDictionary: isn't smart enough to recognize that the value of the key "bar" should be an instance of Bar. It's assigning an NSDictionary to that property. Thus, when you ask for the property "name," the dictionary can't field that request. However, an NSDictionary does know how to handle -valueForKey:, so it happens to work in that case.

So you need to use something smarter than -setValuesForKeysWithDictionary: to populate your objects.

John Calsbeek
  • 35,947
  • 7
  • 94
  • 101
  • 1
    "something smarter," like...? – Old McStopher Feb 09 '12 at 04:10
  • There's no built-in solution, but it's not hard to write one. Assuming your object properties aren't initialized to `nil`, you can write a simple recursive substitute for `-setValuesForKeysWithDictionary:`. It would call `setValue:forKey:` for every value in the dictionary, unless that value is itself a dictionary. At that point, you'd recursively call your function with the result of `valueForKey:` and the dictionary you just found. – John Calsbeek Feb 09 '12 at 07:06
  • Okay. Wasn't sure if there was a natively sanctioned way. That's what I envisioned. Perhaps I could even just put such functionality into some base class that when given a dictionary, parses it and populates itself. – Old McStopher Feb 09 '12 at 16:19
  • @OldMcStopher: That's what I have done and it has seemed to work fine. There are some libraries that do class-level JSON deserialising such as Mantle - but I've found the overhead on it to be more confusing than just doing it manually through enumeration and assignments. – micnguyen Apr 13 '15 at 06:58