I have recently used the following code to extract the ID of a location from a Foursquare API call with:
NSDictionary* foursquareJson = [NSJSONSerialization JSONObjectWithData:secureData options:kNilOptions error:&error];
NSDictionary *venuesDict = foursquareJson[#"response"];
NSArray *venuesArray = venuesDict[#"venues"];
NSDictionary *venuesDict2 = venuesArray[0];
NSArray *categoriesDict = venuesDict2[#"categories"];
NSDictionary *idDict = categoriesDict[0];
NSLog(#"ID is %#",idDict[#"id"]);
with original foursquareJson being:
2015-03-30 17:16:40.700 Voyagic[2833:718563] {
meta = {
code = 200;
};
response = {
venues = (
{
categories = (
{
icon = {
prefix = "https://ss3.4sqi.net/img/categories_v2/building/conventioncenter_";
suffix = ".png";
};
id = 4bf58dd8d48988d1ff931735;
name = "Convention Center";
pluralName = "Convention Centers";
primary = 1;
shortName = "Convention Center";
}
);
contact = {
formattedPhone = "+44 20 7222 5000";
phone = "+442072225000";
};
hereNow = {
count = 0;
groups = (
);
summary = "Nobody here";
};
id = 4b6599d4f964a520f8f52ae3;
location = {
address = "Broad Sanctuary";
cc = GB;
city = London;
country = "United Kingdom";
distance = 2167;
formattedAddress = (
"Broad Sanctuary",
London,
"Greater London",
"SW1P 3EE",
"United Kingdom"
);
lat = "51.49997800145596";
lng = "-0.1289014132864838";
postalCode = "SW1P 3EE";
state = "Greater London";
};
name = "Queen Elizabeth II Conference Centre";
referralId = "v-1427732200";
specials = {
count = 0;
items = (
);
};
stats = {
checkinsCount = 3657;
tipCount = 15;
usersCount = 2407;
};
verified = 0;
}
);
};
}
but there surely must be a better way of accessing the ID which I don't know about (instead of creating 4 dictionaries and 2 array, which seems somewhat excessive :/ ). Any help would be greatly appreciated :)
Ultimately, the data will need to be accessed through the lists of dictionaries and arrays somehow, it just depends on where you want that to happen. You could use or make a parser for the JSON but that will ultimately still need to map the JSON data similarly to what you are doing. A simple and shorter way of accessing the data would be to not create a new variable in every iteration. Although it really is not much different:
NSDictionary *foursquareJson = [NSJSONSerialization JSONObjectWithData:secureData options:kNilOptions error:&error];
NSDictionary *objectId = foursquareJson[#"response"][#"venues"][0][#"categories"][0][#"id"];
NSLog(#"ID is %#",objectId);
Because of the dialogue in the comments of this answer I figured I should probably include a bit more information about your concern with creating "4 dictionaries and 2 arrays". When you use the JSON Serializer to create native objects from the JSON (first line above) you are creating all of the arrays and dictionaries needed to fully represent and store the entire JSON. The difference in code samples between what you originally posted and what I provided is really not a significantly different. If you are concerned with creating too many dictionaries or arrays you should attempt to filter out the JSON prior to deserializing it into native objects.
Related
I have an NSDictionary, named "thisList", and I need to get the value of its key "list_collection".
Xcode indicates that the value is a "NSSingleEntryDictionary".
The value itself contains an array with yet another dictionary.
Please take a look at the screenshot:
Now, no matter what I try (objectforKey/valueforKey) or whatever type of object I initialize (NSArray/NsMutableArray/NSDictionary/NSMutableDictionary) I end up with a nil value.
Apparently, I miss some essential knowledge on how to handle this.
My question: how should I initialize an object with the value of the key "list_collection".
Here is a (partial) dump of the json:
Printing description of thisList:
{
archived = 0;
"chapter_part" = "";
"chapter_title" = "";
comment = "";
"cover_id" = "<null>";
"created_at" = "2017-01-06T12:59:04.000+01:00";
"date_created" = "06 January 2017";
deleted = 0;
id = 141384502;
isMyList = 1;
keywords = (
);
"list_collection" = {
lists = (
{
"speech_locale" = EN;
subject = engels;
words = (
{
word = attitude;
},
The to me most logical approach would be:
NSDictionary * myDic = [thisList objectForKey:#"list_collection"];
Note: I didn't explicitly initialize 'myDic' here.
To put things in context, here is my code:
NSString * hlists = [json objectForKey:#"lists"];
NSData* data = [hlists dataUsingEncoding:NSUTF8StringEncoding];
NSArray *wrtsLists = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonParsingError];
Lists = [[NSMutableArray alloc]init];
for (NSDictionary *thisList in wrtsLists){
WordList* theList = [[WordList alloc]init];
theList.title = [thisList valueForKey:#"title"];
[Lists addObject:theList];
NSDictionary *myDic = thisList[#"list_collection"];
>>>>this is where I put a breakpoint. myDic is nil here
}
Thanks for your insights.
You can simply get "list_collection" array from "thisList" using by this code
NSDictionary *myDic = thisList[#"list_collection"];
In the end, I figured out, that it was an Xcode problem.
The Debug area just didn't correctly display the values of my objects.
I restarted Xcode, and things started to work as expected.
I lost several hours of my life on this. But I learned a lot, thanks to your good advises.
Try this hope it helps You
NSDictionary *myDic = [thelist valueForKey:"list_collection"];
My application has an NSDictionary containing many other NSDictionary inside it. If I print out this dictionary it reads as follows:
oxip = {
created = "2014-02-10 14:42:59";
lastMsgId = "";
requestTime = "1.6434";
response = {
code = 001;
debug = "";
message = success;
request = getHierarchyByMarketType;
text = "\n";
williamhill = {
class = {
id = 1;
maxRepDate = "2014-02-10";
maxRepTime = "07:31:48";
name = "UK Football";
text = "\n";
type = (
{
id = 2;
lastUpdateDate = "2013-12-26";
lastUpdateTime = "13:32:54";
market = (
{
betTillDate = "2014-02-15";
betTillTime = "15:00:00";
date = "2014-02-15";
id = 140780553;
lastUpdateDate = "2014-02-10";
lastUpdateTime = "14:09:13";
name = "Queen of the South v Dundee - Match Betting";
participant = (
{
handicap = "";
id = 496658381;
lastUpdateDate = "2014-02-10";
lastUpdateTime = "14:09:13";
name = Dundee;
odds = "11/8";
oddsDecimal = "2.38";
text = "\n\n\n\n\n\n";
},
{
handicap = "";
id = 496658380;
lastUpdateDate = "2014-02-10";
lastUpdateTime = "14:09:13";
name = Draw;
odds = "5/2";
oddsDecimal = "3.50";
text = "\n";
},
{
handicap = "";
id = 496658379;
lastUpdateDate = "2014-02-10";
lastUpdateTime = "14:09:13";
name = "Queen of the South";
odds = "11/8";
oddsDecimal = "2.38";
text = "\n";
}
);
text = "\n";
time = "15:00:00";
}
What is the best possible way for my application to reach the NSDictionary with the name of:
name = "Queen of the South v Dundee - Match Betting"
without the need of going through each individual dictionary and finding its object for key?
You can use valueForKeyPath for that. It accepts a path, separated by dots. Example:
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:#"https://dl.dropboxusercontent.com/u/1365846/21680479.json"]]
options:0
error:nil];
NSLog(#"%#", [dict valueForKeyPath:#"response.williamhill.class.type.market.name"]);
This depends on the representation of dictionary. If the williamhill part is changing, then it does not work, of course.
There is no way to obtain a reference within a map data type (in this case, an NSDictionary) without traversing it. Think of the simplified version of this problem: You have a linked list with N elements and you wish to reach the N'th element. Because this is a linked list, you'll have to go through all other N-1 nodes in order to obtain the last reference.
An NSDictionary is a hash based data type in which keys and values are stored. In the case you describe, you have no reference to the nested object (an NSDictionary itself) so you must also traverse all of the dictionaries containing it.
Hope this helps point you in the right direction.
When I convert the JSON into an NSDictionary then call ObjectForKey: I get the error: -[__NSCFArray objectForKey:]: unrecognized selector sent to instance 0xa9801d0
My code for converting:
NSError *error;
NSData *responseData = [NSData dataWithContentsOfURL:dataURL];
NSDictionary *json = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
NSLog(#"%#",json);
//The exeption is hilighted here:
NSArray *objects = [[json objectForKey:#"data"] objectForKey:#"children"];
This is the file:
{
data = {
after = "<null>";
before = "<null>";
children = (
{
data = {
"approved_by" = "<null>";
author = Jonovono;
"author_flair_css_class" = "<null>";
"author_flair_text" = "<null>";
"banned_by" = "<null>";
clicked = 0;
created = 1371955129;
"created_utc" = 1371926329;
distinguished = "<null>";
domain = "self.redditdev";
downs = 1;
edited = 0;
hidden = 0;
id = 1gv8g1;
"is_self" = 1;
likes = "<null>";
"link_flair_css_class" = "<null>";
"link_flair_text" = "<null>";
media = "<null>";
"media_embed" = {
};
name = "t3_1gv8g1";
"num_comments" = 2;
"num_reports" = "<null>";
"over_18" = 0;
permalink = "/r/redditdev/comments/1gv8g1/rate_limiting_on_commenting/";
saved = 0;
score = 4;
selftext = "Can't really find anyone else having this issue. I am using this ruby library for reddit api wrapper: https://github.com/paradox460/snoo\n\nIt seems when I comment only the first one goes through, and any others fail. It does not seem to return a message, but I am guessing it's because of the message that only comment so much in so much time. How can I get around that? Or is something else causing it? Thanks.";
"selftext_html" = "<!-- SC_OFF --><div class=\"md\"><p>Can't really find anyone else having this issue. I am using this ruby library for reddit api wrapper: <a href=\"https://github.com/paradox460/snoo\">https://github.com/paradox460/snoo</a></p>\n\n<p>It seems when I comment only the first one goes through, and any others fail. It does not seem to return a message, but I am guessing it's because of the message that only comment so much in so much time. How can I get around that? Or is something else causing it? Thanks.</p>\n</div><!-- SC_ON -->";
subreddit = redditdev;
"subreddit_id" = "t5_2qizd";
thumbnail = "";
title = "Rate limiting on commenting?";
ups = 5;
url = "http://www.reddit.com/r/redditdev/comments/1gv8g1/rate_limiting_on_commenting/";
};
kind = t3;
}
);
modhash = 6dviotq5igca155758e9e858f1d863870f1b5296d9d571d45d;
};
kind = Listing;
},
{
data = {
after = "<null>";
before = "<null>";
children = (
{
data = {
"approved_by" = "<null>";
author = pipeep;
"author_flair_css_class" = "<null>";
"author_flair_text" = "<null>";
"banned_by" = "<null>";
body = "New accounts are severely rate limited on comment speed to prevent spamming. Once you accumulate some karma, that goes down rather quickly. I've found that my bot with a few thousand karma is able to post a few times a minute at peek without any problems.\n\nThe API returns an error if you post too quickly (Probably `RATELIMIT` or `SUBREDDIT_RATELIMIT`; I don't remember). You can either drop those comments or put them in a queue and retry later, hoping that your queue doesn't get too long.";
"body_html" = "<div class=\"md\"><p>New accounts are severely rate limited on comment speed to prevent spamming. Once you accumulate some karma, that goes down rather quickly. I've found that my bot with a few thousand karma is able to post a few times a minute at peek without any problems.</p>\n\n<p>The API returns an error if you post too quickly (Probably <code>RATELIMIT</code> or <code>SUBREDDIT_RATELIMIT</code>; I don't remember). You can either drop those comments or put them in a queue and retry later, hoping that your queue doesn't get too long.</p>\n</div>";
created = 1371970411;
"created_utc" = 1371941611;
distinguished = "<null>";
downs = 0;
edited = 0;
gilded = 0;
id = cao9eaf;
likes = "<null>";
"link_id" = "t3_1gv8g1";
name = "t1_cao9eaf";
"num_reports" = "<null>";
"parent_id" = "t3_1gv8g1";
replies = {
data = {
after = "<null>";
before = "<null>";
children = (
{
data = {
"approved_by" = "<null>";
author = Jonovono;
"author_flair_css_class" = "<null>";
"author_flair_text" = "<null>";
"banned_by" = "<null>";
body = "Alright, thanks. Good to know it goes down with karma. So karma is good for something.";
"body_html" = "<div class=\"md\"><p>Alright, thanks. Good to know it goes down with karma. So karma is good for something.</p>\n</div>";
created = 1372014449;
"created_utc" = 1371985649;
distinguished = "<null>";
downs = 0;
edited = 0;
gilded = 0;
id = caoivdj;
likes = "<null>";
"link_id" = "t3_1gv8g1";
name = "t1_caoivdj";
"num_reports" = "<null>";
"parent_id" = "t1_cao9eaf";
replies = "";
"score_hidden" = 0;
subreddit = redditdev;
"subreddit_id" = "t5_2qizd";
ups = 1;
};
kind = t1;
}
);
modhash = 6dviotq5igca155758e9e858f1d863870f1b5296d9d571d45d;
};
kind = Listing;
};
"score_hidden" = 0;
subreddit = redditdev;
"subreddit_id" = "t5_2qizd";
ups = 1;
};
kind = t1;
}
);
modhash = 6dviotq5igca155758e9e858f1d863870f1b5296d9d571d45d;
};
kind = Listing;
}
)
I need to be able to get any object from the JSON but I do not know why this exception is being thrown.
Do you see that lonely ) right at the end of the JSON output you've provided. If you look at the actual log from Xcode you're likely to see that there is a matching ( which you've forgotten to copy and paste into your question.
What you actually have is JSON object that is an array not a dictionary, so when you are trying to get values for keys, the compiler is telling you that the array doesn't respond to that message.
That should help you unpack your JSON object, you need to iterate through an array of dictionaries extracting the values you need.
I will complete #Abizem answer by adding a short piece of code because I see that you are a beginner (I don't have any problem with that) and I know that when you start programming or a new language, code snippets are really useful (doesn't meter that google is full of JSON parsing tutorials and SO is full of posts that are having your problem unrecognized selector when parsing.)
So whenever you have a JSON that starts with ( it means that the JSON is an array, if the JSON starts with a { then the JSON is a dictionary.
In order to get objects from a JSON array you will have to iterate over it. The objects contained by the JSON can be dictionaries or arrays.
So if you have only dictionaries into your array you should use:
for(NSDictionary *contentDictionary in yourJSONObject) {
//do stuffs with your dictionary
}
If you have only arrays into your JSON you should use:
for(NSArray *contentArray in yourJSONObject) {
//do stuffs with your dictionary
}
If you have both arrays and dictionaries in your JSON you should use:
for(id unknownType in yourJSONObject) {
if([unknownType isKindOfClass:[NSDictionary class]]) {
NSDictionary *dict = (NSDictionary*)unknownType;
}
else if ([unknownType isKindOfClass:[NSArray class]]{
NSArray *array = (NSArray *)unknownType;
}
}
If your JSON is a dictionary then you can use the NSDictionary methods to get the proper values:
[dictJSON objectForKey:#"myKey"];
The JSON is an array not a dictionary. Get the first element from the array before you access it as a dictionary.
Your object json is an NSArray which you're trying to access like a NSDictionary (objectForKey:)
I'm really stuck right now while using BZForursquare to get nearby Venues into a UITableView.
BZFoursquare: https://github.com/baztokyo/foursquare-ios-api
I get my Requestresult inside the requestDidFinishLoading Delegate Method. In this Method the request Object contains several NSDictionaries and one Dictionary is in request.response. This response Dictionary contains one entry with key="venues" and as Value a JSON Object. When I put this value Object into a dictionary the type seems not to be a Dictionary but a NSCFArray:
#pragma mark BZFoursquareRequestDelegate
- (void)requestDidFinishLoading:(BZFoursquareRequest *)request {
self.meta = request.meta;
self.notifications = request.notifications;
self.response = [request.response objectForKey:#"venues"];
self.request = nil;
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
NSLog(#"%#",[self.response objectForKey:#"name"]);
}
I assume this because the NSLog Line gives me the following error:
-[__NSCFArray objectForKey:]: unrecognized selector sent to instance 0x1e5c90f0
Now I'm totaly confused and tried some failed attempts to get this JSON from whatever kind od Datatype it is into a NSDictionary. One attempt was to put the value Object into an NSString and use
[NSJSONSerialization JSONObjectWithData:[responseString dataUsingEncoding:NSUTF8StringEncoding] options:0 error:&error];
to get it into a Dictionary but that also failed because it still remains a NSCFArray. Can someone please tell me how I get content of
[request.response objectForKey:#"venues"]
into a NSDictionary so that I can populate my UITabelview with this content?
EDIT:
Here is whats in the value part from the Dictionary request.response:
(
{
categories = (
{
icon = {
name = ".png";
prefix = "https://foursquare.com/img/categories/food/default_";
sizes = (
32,
44,
64,
88,
256
);
};
id = 4bf58dd8d48988d10b941735;
name = "Falafel Restaurant";
pluralName = "Falafel Restaurants";
primary = 1;
shortName = Falafel;
}
);
contact = {
};
hereNow = {
count = 0;
groups = (
);
};
id = 4df3489dfa76abc3d86c4585;
likes = {
count = 0;
groups = (
);
};
location = {
cc = DE;
city = "Vinn";
country = Germany;
distance = 92;
lat = "51.44985";
lng = "16.648693";
state = "Nordrhein-Westfalen";
};
name = "Yildiz D\U00f6ner";
specials = (
);
stats = {
checkinsCount = 3;
tipCount = 0;
usersCount = 2;
};
verified = 0;
}
And this seems to be from Type of NSCFArray. And how can I create from this another Dictionary so that I can access the JSON Values by key? Sorry if I'm really slow today...
You ask for "venues" which I assume is an array of such. So after deserializing the json, log the return object to see what you get. It's almost for sure an array of dictionaries.
I'm trying to get the nearby places using the foursquare api.
Here's the json data that is returned from
NSDictionary *results = [jsonString JSONValue];
NSLog(#"%#", results);
(
{
code = 200;
errorDetail = "This endpoint will stop returning groups in the future. Please use a current version, see http://bit.ly/lZx3NU.";
errorType = deprecated;
},
{
groups = (
{
items = (
{
categories = (
{
icon = "https://foursquare.com/img/categories/parks_outdoors/default.png";
id = 4bf58dd8d48988d163941735;
name = Park;
parents = (
"Great Outdoors"
);
pluralName = Parks;
primary = 1;
shortName = Park;
}
);
Then I try to get the list of the groups in an array with
NSArray *groups = [ (NSDictionary *)results objectForKey:#"groups"];
This returns the following error
2011-11-05 11:42:12.907 XperienzApp[1972:207] No of results returned: 0 Results : (null)
2011-11-05 11:42:13.225 XperienzApp[1972:207] -JSONValue failed. Error trace is: (
"Error Domain=org.brautaset.JSON.ErrorDomain Code=3 \"Unrecognised leading character\" UserInfo=0x5849cd0 {NSLocalizedDescription=Unrecognised leading character}"
)
2011-11-05 11:42:13.225 XperienzApp[1972:207] No of results returned: 0 Results : (null)
How should I parse this?
Edit:
I tried the suggested technique, this gives me an array
id groups = [[(NSDictionary *)results objectForKey:#"response"] objectForKey:#"groups"];
if ([results count] > 1){
NSLog(#"groups class %#\ngroups %# %d", groups, [groups class], [groups count]);
The log output is of the form:
{
categories = (
{
icon = "https://foursquare.com/img/categories/nightlife/danceparty.png";
id = 4bf58dd8d48988d11f941735;
name = Nightclub;
parents = (
"Nightlife Spots"
);
pluralName = Nightclubs;
primary = 1;
shortName = Nightclub;
}
);
contact = {
};
hereNow = {
count = 0;
};
id = 4eb33ba561af0dda8f673c1b;
location = {
address = "144 Willow St 4R";
city = Brooklyn;
crossStreet = Pierrepont;
distance = 462;
lat = "40.696864";
lng = "-73.996409";
postalCode = 11201;
state = NY;
};
name = "Entertainment 720, Ltd.";
stats = {
checkinsCount = 3;
tipCount = 0;
usersCount = 1;
};
verified = 0;
}
);
name = Nearby;
type = nearby;
}
)
groups __NSArrayM 1
This is again not json and is hard to parse, how do I get the output in json.
I'm the iPhone lead at foursquare. I'll try to take a stab at what's going on here.
First of all, I highly recommend you use JSONKit for your parser. It's lightweight and insanely fast: https://github.com/johnezang/JSONKit
It appears that you are parsing the JSON properly and getting the dictionary properly. Then you are logging the parsed object, not the original JSON. The output you are seeing is how Objective-C chooses to serialize the parsed dictionary to text. It is definitely not JSON. Using JSONKit, you could send the JSONString selector to your parsed result and convert it back to JSON and log that.
If you could provide some details on the problem you are trying to solve, I might be able to help you out more. And as Maudicus said, please pay attention to the error you are getting back. You don't want your app to break when we make the change to the API.
If the output below NSLog(#"%#", results); is your log statement. It appears your results variable is an array of dictionary objects.
Try to log the class of results to verify that NSLog(#"%#", [results class]);
If it is an array your groups object is the second object.
if ([results count] > 1)
id groups = [results objectAtIndex:1];
NSLog(#"groups class %#\ngroups %#", [groups class], groups);
Keep doing this until you understand the format of your data
Also the line
errorDetail = "This endpoint will stop returning groups in the future. Please use a current version, see http://bit.ly/lZx3NU.";
should be cause for concern. Check the documentation on foursquare for the current way of getting groups.