objective-c JSON literals, nested - most elegant solution? - ios

Say you do this,
NSString *teste = yourData[#"title"];
no problem if "title" is completely missing in the json: you just get null. If you do this:
NSString *teste = yourData[#"location"][#"city"];
if "city" is missing in the json nest, no problem. if the whole "location" section does not exist, again no problem
However! You'll often see json like this, " largeImage = "<null>"; "
In that case, the app will crash if you are using the code above.
In practice you have to do this:
NSString *imageUrl = nil;
if ([yourResults[thisRow][#"largeImage"] isKindOfClass:[NSDictionary class]])
imageUrl = yourResults[thisRow][#"largeImage"][#"URL"];
My question was really:
is there some dead clever way to perhaps override the literal syntax (ie, override the underlying message, perhaps??) to cover this problem?
So essentially, make this concept [#"blah"] basically first check that indeed it is a dictionary at hand, before trying the operation.
It's a shame because, effectively, you can never use this wonderful syntax
yourData[#"location"][#"city"]
in practice, due to the problem I outline.
PS sorry for the earlier confusion on this question, fixed by Paramag. below - good one Paramag.

Personally i use with JSON category which returns null instead NSNull so my code looks:
[[json objectForKeyNotNull:#"Key"] objectForKeyNotNull:#"Other"]
As you want to have code shorter, i think i would create the category on NSDictionary which could be used as :
[json objectForPath:#"Key.Value"]
Which would expand the path into the keys.
There is some nice gist which looks like it's doing it:
https://gist.github.com/Yulong/229a62c1188c3c024247#file-nsdictionary-beeextension-m-L68

to check this kind of null , you can use valueForKeyPath:
NSString *teste = [CLOUD.yourData[thisRow] valueForKeyPath:#"location.city"];
it will first check for "location" and then for "city".

I would say this is a problem with your schema. null in JSON and a dictionary (called an "object") in JSON are different types of things. The fact that your key can have a value that is sometimes null and sometimes a dictionary seems to me that you guys are not following a rigorous schema.
A good design would have the key either not be there, or if it is there, its value is guaranteed to be a dictionary. In such a case, the Objective-C code NSString *teste = yourData[#"location"][#"city"]; would work without modification because if the key "location" didn't exist, then its value would be nil in Objective-C, and subsequent accesses won't crash, and will also return nil.

Related

Create NSDictionary from Dictionary Log printed by Xcode

Is it possible to create Dictionary from string data that log area of xcode display. Like
0 = {
"Key1" = "ABC";
};
1 = {
"Key1" = "DEF";
};
No way whatsoever. The output of NSLog is purely for debugging purposes. You have no chance in hell reconstructing a dictionary from this. Don't even try.
What are you actually trying to achieve?
It looks to me, as you are printing an array, containing two NSDictionary objects.
When you write it out to the console, then you must know the object type (or can find it, using the debugger).
If its an array, you can loop through it, taking out the individual key and values, and adding them to a new NSMutableDisctionay, that then will contain a all keys/values. Just ensure that your keys are unique.

How can i retrieve "extract" field from wikipedia api?

This is the link which I have to parse it in Objective C:
http://en.wikipedia.org/w/api.php?action=query&prop=extracts&format=json&exsentences=2&exintro=&titles=USA
If you clearly look at the link the word "USA" is actually going to change as per user requests.In the JSON information after pages key you can actually see a number before pageid. How can I access it? If that field is static I can access with that particular key. But it seems like that number is dynamic according to the user search. My aim is to access "extract" key. If I want to do that I have to go inside to that number which is a dynamic one. I appreciate any kind of help. Thanks in advance.
Under "pages" you are getting a dictionary with keys that are the same as the ids (just strings). It seems your question circles around the fact that you do not know these keys beforehand.
Once you extracted the pages dictionary, you can iterate through the keys like this:
for (NSString *idString in pages.allKeys) {
NSDictionary *content = pages[idString];
NSString *extract = content["extract"];
// do something with extract
}
If you just want the any key or you are sure there is only one, you can instead create the content dictionary with
NSDictionary *content = pages[pages.allKeys.firstObject];
One expression doing the same as Mundi's answer:
NSString *extract =
[[[array valueForKeyPath:#"query.pages"] allObjects].firstObject
valueForKey:#"extract"];

NSMutableDictionary contents inconsistent with output of allValues

So long story short, there's a discrepancy between the output of a NSMutableDictionary's contents and the result of calling allValues on the same object. Below is some debugger output after inspecting the object which demonstrates my problem: (made generic of course)
(lldb) po self.someDict.allKeys
<__NSArrayI 0xa5a2e00>(
<SomeObject: 0xa5a2dc0>,
<SomeObject: 0xa5a2de0>
)
(lldb) po self.someDict.allValues
<__NSArrayI 0xa895ca0>(
0.5,
0.5
)
(lldb) po self.someDict
{
"<SomeObject: 0xa5a2dc0>" = (null);
"<SomeObject: 0xa5a2de0>" = (null);
}
So as we can see, the actual output of the NSMutableDictionary contains null values for both its entries, but the contents of .allValues contains the proper data. These three outputs were taken at the same time in execution.
I'm not sure why this is happening, but I think it may have something to do with the fact that I'm encoding/decoding the object which this dictionary is a property of using CoreData. I believe I'm doing this properly:
[aCoder encodeObject:self.someDict forKey:#"someDict"];
and to decode
self.someDict = [aDecoder decodeObjectForKey:#"someDict"];
The weird thing is that if I inspect the dictionary before it ever gets encoded, it is still in the state described at the beginning of the post, so this is why I'm doubtful the CoreData actions are screwing with the contents.
As always, please don't hesitate to request additional information.
EDIT: The problem was as answered below. I was using a custom class which didn't cooperate with isEqual, so my solution was to change the storage and structure of my logic, which made using a Dictionary unnecessary.
I have not been able to duplicate the problem using NSString as keys and NSNumber as values. I suspect that your custom class does not properly implement hash and/or isEqual. Specifically, the results from those two methods must be consistent, meaning that if isEqual returns true, then the hash values for the two objects must be identical.
You also need to ensure that your class implements NSCopying properly and that a copy is equal to the original.
As a general rule, don't use custom objects for dictionary keys. Just use strings and be done with it.
As user3386109 points out, custom objects must properly implement the -hash and -isEqual methods in order to be used as dictionary keys, and even then, custom objects don't work correctly for dictionary keys for things like key/value coding.

Objective C converting NSDictionary valueForKeyPath into a string

I have a NSDictionary that I am trying to get some strings out of. The name of the dictionary is called tweets. I can get the user name and tweet by doing the following.
NSDictionary *tweet = self.detailItem;
NSString *username = [[tweet objectForKey:#"user"] objectForKey:#"name"];
NSString *tweetText = [tweet objectForKey:#"text"];
If I NSLog the results I get; username is "person's username" and tweetText is "person's tweet." These are both NSString that I can manipulate.
However, I also need to get the actual URL from the tweet, not the shortened one. So I have found it in the dictionary with valueForKeyPath. I need this as a string and have tried the following.
NSString *url = [tweet valueForKeyPath:#"entities.urls.display_url"];
I can NSLog this as well and I get the proper information. But I think it comes out as an Array? The NSLog looks like this.. url is ( "url goes here" ). With the brackets and quotes. This is not a string like the others. I am assuming it is because of the valueForKeyPath. When I tried to use url hasPrefix, it doesn't like that and mentions you can't have hasPrefix with and array. But I also tried to use multiple objectForKey with entities, urls, and display_url. But this does not work. Is there any way to get my entry into a string format. I have looked over some SO questions and on the web, but can't find anything that is useful for me. Any help will by much appreciated. Thanks for your time
The keyPath entities.urls.display_url traverse an array of arrays and flattens that to return an array of display_urls. If you were confident that there would be only one url within that hierarchy you could use
NSString *url = [[tweet valueForKeyPath:#"entities.urls.display_url"] lastObject];
If there are multiple entities with URLs you may have to traverse the entities and urls to determine the URL in which you are interested. Or you may be able to use firstObject if what you are really looking for is entities.urls.[0].display_url
Per Twitter's documentation on entities, entities is a dictionary and urls is an array of dictionaries.
When you perform ... valueForKeyPath:#"entities.urls.display_url"] you do the same thing as ... valueForKey:#"entities"] valueForKey:#"urls] valueForKey:#"display_url"].
Per NSDictionary's documentation, its valueForKey: returns the same thing as objectForKey: if — as in this case — the key in question doesn't start with an #.
So the entities part of valueForKeyPath: returns an array of objects.
Per NSArray's documentation, its valueForKey: returns an array comprised of the result of calling valueForKey: on each object in the array individually.
Given that each thing in the entities array is a dictionary, what you therefore get back is an array of the display_url key for every entity in the tweet.
So I think your problem is that you expect to get "the actual URL from the tweet" (emphasis added). Tweets may contain arbitrary many links outward, not merely one — entities represent ranges of characters with special meanings like links, hashtags and similar. So you end up with an array rather than a single value.

Strange brackets?

Please anybody now how to remove strange brackets?
Tasting *tasting = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.creationDate.text = [[tasting valueForKey:#"creationDate"] description];
cell.wineName.text = [[[tasting wine]valueForKey:#"wineName"]description];
OK, so the object stored as wineName is an NSSet, which explains why you are getting the value in the form {( "name" )} when calling the description method. Exactly why it's an NSSet object is unknown to me, given a wine a generally marketed under just one name...
To get it in your preferred format don't use description, and instead pull the value out and display like this:
NSSet *wines = [[tasting wine] valueForKey:#"wineName"];
cell.wineName.text = [wines anyObject];
You will also want to do something similar with the date column, using an NSDateFormatter object to format it to the user's preferred format.
Actually wineName here is an NSSet (you can tell that by the {(...)} from the object description and of course by the log of the class) containing your string.
Assuming that your Set will always contain just one object, you could get it like this:
cell.wineName.text = [[[tasting wine] valueForKey:#"wineName"] anyObject];
Though, I do not think that an NSSet is the best option to hold some object's name...
Finally, as a sidenote... using description for presenting data to the user is a bad choice (the method's intended use is for debugging purposes)

Resources