views:

5884

answers:

4

I'm using json-framework to create a NSDictionary out of a JSON response. That much works wonderfully.

Now, within this JSON payload are one or more objects - let's call them X. Sort of like this in XML:

<OBJECTS>
  <X>
    ...
  </x>
  <X>
    ...
  </X>
  <X>
    ...
  </X>
</OBJECTS>

When I look in the aforementioned NSDictionary object for all Xs, like so:

NSDictionary *results = [[dict objectForKey:@"OBJECTS"] objectForKey:@"X"];

Or even:

NSDictionary *results = [dict valueForKeyPath:@"OBJECTS.X"];

I get, according to gdb, a NSCFArray of NSDictionary objects. (Yes, I smell something funny here too, but more on this in a moment.)

When there is only one object named X, I get back an honest-to-goodness NSDictionary.

So ... what should I do to make this behave consistently, no matter how many Xs there are?

At first glance, I'd think I just change results to be NSArray *, but what happens when I want to fast enumerate over the results? Right now I do this:

for (NSDictionary *result in results)

In the NSCFArray case (multiple Xs), I get back an individual NSDictionary for each X. In the single X case, I get back the one NSDictionary, except now my perspective is one level too deep. In other words, instead of this (contrived example):

(gdb) po results
<NSCFArray 0xd4a940>(
{
    foo =     {
        bar = "something";
    };
}
{
    foo =     {
        bar = "something else";
    };
}
)

I get this:

(gdb) po results
{
    foo =     {
        bar = "something";
    };
}

Clues welcome/appreciated! You may even ask if it's necessary to have to break this apart at all, but for now let's suppose this trip is really necessary. (Still, I'm happy to be persuaded otherwise, if someone feels strongly enough about it.)

Ultimately, at the end of the day, I want to have a NSArray of NSDictionary objects.

A: 

I am not familiar with JSON or the json-framework, but clearly objectForKey cannot be used to access the X's since they all have the same key.

If you know that objectForKey:@"OBJECTS" will return either an NSDictionary (single element) or an NSArray of NSDictionarys (multiple X elements), then you could do something like:

if ( ![results isKindOfClass:[NSArray class]] ) {
    results =[NSArray arrayWithObject:results];
}

That will get you a consistent result, assuming you understand exactly how the json-framework will behave. It will be somewhat fragile, if the elements ever return an array of entries instead of an NSDitionary then it will all fall apart.

There may be a configuration setting for the json-framework that lets you control how it behaves in this case and that would be preferable.

Peter N Lewis
Ahh, I didn't realize objectForKey can't be used to access multiple instances of the same key - thanks! (Though it appears to be doing exactly that with the NSCFArray ... or I'm hallucinating ... or it's just undefined behavior and I got lucky.)I just happened across this article, and it handles the case of multiple X's, as it were, so I'm going to give this a try next. If it works, I'll summarize the fix and report back here!http://iphonedevelopertips.com/cocoa/json-framework-for-iphone-part-2.html
Joe D'Andrea
Meanwhile, here's the member function I'm using. Note that it returns a dictionary or array. Barring any library mods, perhaps if I can just test for a dictionary, I can assign it to a one-object array if necessary.http://json-framework.googlecode.com/svn/trunk/documentation/interfaceNSString_07NSString__SBJSON_08.html
Joe D'Andrea
I was wrong about the multiple instances of the same key. (Of course, I mean, if that's how NSDictionary works, then that's how it works!) It turns out we do indeed have an array of dictionaries, which is exactly what you surmised. :)
Joe D'Andrea
A: 

Here's what I ended up doing:

// Get the JSON response and return the result:
NSString *jsonString = [NSString stringWithContentsOfURL:url];
NSDictionary *dict = [jsonString JSONValue];

// If OBJECTS.X gets a dictionary (one value), make an array of one.
id myObject = [dict valueForKeyPath:@"OBJECTS.X"];
if ([myObject isKindOfClass:[NSDictionary class]]) {
    results = [NSArray arrayWithObject:myObject];
} else {
    results = myObject;
}

It's worth pointing out that JSONValue can also return a dictionary or an array. However, I'm looking inside the dictionary post-assignment (OBJECTS.X).

Joe D'Andrea
While we're at it, it's also worth pointing out Peter's suggestion is the more concise one! We know we're going to get back a dictionary or an array, so in Peter's case, if it's not an array, it's a dictionary and we can just create a one-object array out of it.
Joe D'Andrea
A: 

I don't think your two code examples,

NSDictionary *results = [[dict objectForKey:@"OBJECTS"] objectForKey:@"X"];

and

NSDictionary *results = [dict valueForKeyPath:@"OBJECTS.X"];

are equivalent. Key-Value-Coding has some extra smarts to work with collections, which might be throwing you off here. (An example can be found in the blog post Theocacao: NSArray and KVC by Scott Stevenson.) If you revert back to the former you might get the behaviour you expect.

Stig Brautaset
Thanks! In this particular case, "po results" shows the exact same thing for both of those code examples. (Scout's honor!) Since you can't have more than one "X" in a given dictionary, each "X" in this case is it's own NSDictionary object.However ... there may be another reason not to use valueForKeyPath:. :-ohttp://belkadan.com/blog/2007/10/Performance-Optimization-Why-We-Cannot-Use-valueForKeyPath/
Joe D'Andrea
I should clarify: If I look at the object for key OBJECTS, I get a single dictionary. If I look in there for the key X, _that_ ends up being array of dictionaries, since there are multiple X's in the original XML/JSON.
Joe D'Andrea
+1  A: 

NSDictionary *results = [[dict objectForKey:@"OBJECTS"] objectForKey:@"X"];

and

NSDictionary *results = [dict valueForKeyPath:@"OBJECTS.X"];

The above two are exactly same. The first operation is a little costlier operation than the second one.

prav