Objective-C for in loop nested NSDictionaries - iphone

I've got this data I get from an XML feed and parse as NSDictionaries. The data structure is like this:
item = {
address = {
text = "1 Union Street";
};
data = {
text = "Hello.";
};
event_date = {
text = "2012-02-27";
};
event_type = {
text = pubQuiz;
};
latitude = {
text = "57.144994";
};
longitude = {
text = "-2.10143170000003";
};
name = {
text = "Mobile Tester";
};
picture = {
text = "public://pictures/event_default.png";
};
time = {
text = "23.00";
};
vname = {
text = Test;
};
}
//More items
Each sub-section, ie, address, data, event_date etc are all NSDictonaries.
I would like to iterate through the whole collection and grab the "text" inside each of the sections to create an array of "items" with these properties. I've tried using the for..in loop structure in Objective-C but haven't had any success so far. Has anyone got a few pointers? I'm happy to read through any good tutorials or examples on how to traverse nested NSDictionaries.
EDIT:
Alright so, after parsing the XML I get that structure up there. What I would like to do is traverse the "item" structure to extract the "text" fields of all the inner dictionaries - something like this:
foreach(item in array of dictionaries) {
myItem.address = item.address.text;
myItem.data = item.data.text;
...
}
And so on. I hope this makes it a little clearer.
So, the part where I'm stuck is where I want to set the properties of the item while traversing the NSDictionaries. This is what I have so far:
for(NSDictionary *item in self.eventsArray) {
for (NSDictionary *key in [item allKeys]) {
NSLog(#"%#", key);
id value = [item objectForKey:key];
if ([value isKindOfClass:[NSDictionary class]]) {
NSDictionary* newDict = (NSDictionary*)value;
for (NSString *subKey in [newDict allKeys]) {
NSLog(#"%#", [newDict objectForKey:subKey]);
}
}
}
}
Which outputs:
address
1 Union Street
data
Hello.
...
I'm just not sure how to select the proper attribute in the object I'm creating to set the required property, if that makes any sense?

You can create a new dictionary called items from the original one (called here dictionaries) :
NSMutableDictionary *items = [[NSMutableDictionary alloc] init];
for (NSDictionary *dict in dictionaries)
{
[items setValue:[dict objectForKey:#"text"] forKey:[dict description]];
}
It will create the following dictionary:
item = {
address = "1 Union Street",
data = "Hello.",
....
}

To get the items I did the following:
id eventsArray = [[[[XMLReader dictionaryForXMLData:responseData error:&parseError] objectForKey:#"root"] objectForKey:#"events"] objectForKey:#"event"];
if([eventsArray isKindOfClass:[NSDictionary class]])
{
//there's only one item in the XML, so there's only an NSDictionary created.
for(NSString *item in (NSDictionary*)eventsArray)
{
//check what each item is and deal with it accordingly
}
}
else
{
//There's more than one item in the XML, an array is created
for(NSDictionary *item in (NSArray*)eventsArray)
{
for (NSString *key in [item allKeys])
{
//check what value key is and deal with it accordingly
}
}
}
And that allows me to access all elements.

Related

How to create an array with particular item from a dictionary?

I have an application in which i am having the details of the members as a dictionary.i want to add an array with particular object from the dictionary.The response i am having is like this,
{
500 = {
name = baddd;
status = "<null>";
};
511 = {
name = abyj;
status = "Hi all...:-)";
};
512 = {
name = abyk;
status = fdffd;
};
}
I want to create an array with the results of name only.i have tried like this
for(int i=0;i<=self.currentChannel.memberCount;i++)
{
NSString *name=[NSString stringWithFormat:#"%#",[self.currentChannel.members objectForKey:#"name"]] ;
NSLog(#"%#",name);
[searchfriendarray addObject:name];
}
NSLog(#"%#",searchfriendarray);
but the value added is null. can anybody help me ?
Traverse objectEnumerator to get the values (inner dictionaries). Then just add the value of "name" to the resulting array. Example (assuming the dictionary is named d):
NSDictionary* d = ...
NSMutableArray* array = [NSMutableArray arrayWithCapacity:d.count];
for(NSDictionary* member in d.objectEnumerator) {
[array addObject:[member objectForKey:#"name"]];
}
Krumelur was faster than me ;) He is right by saing that you should traverse the dictionary values first. In your implementation you don't reference your counter variable i somewhere, so the NSString name is the same in each iteration.
This may help you..
// here you can get Array of Dictionary First
NSArray *arr = [[NSArray alloc] initWithContentsOfFile:#""]; // get array first from your response
NSDictionary *temp = [[NSDictionary alloc] initWithDictionary:self.currentChannel.members];
for(int i=0;i<=self.currentChannel.memberCount;i++)
{
NSDictionary *temp = [arr objectAtIndex:i];
NSString *name=[NSString stringWithFormat:#"%#",[temp objectForKey:#"name"]] ;
NSLog(#"%#",name);
[searchfriendarray addObject:name];
}
NSLog(#"%#",searchfriendarray);
Thanks.

Filtering an NSArray from JSON?

I'm trying to implement a searchable tableview in my app, where when someone can search a location and get results. It looks something like this:
I'm getting my source from genomes.com which gives more then just cities, it also has parks, buildings, counties, etc. I want to just show locations which are cities.
The data is a JSON file which is parsed by JSONKit. The whole file comes in (maximum 20 objects) and then the searchable table view shows it. I'm not sure if I should parse the JSON file differently, or if I should make the table view show only the results needed. (Performance in this case is not an issue.). The JSON file gets converted to an NSArray.
Here is part of the array:
{
adminCode1 = MA;
adminCode2 = 027;
adminName1 = Massachusetts;
adminName2 = "Worcester County";
adminName3 = "";
adminName4 = "";
adminName5 = "";
continentCode = NA;
countryCode = US;
countryName = "United States";
elevation = 178;
fcl = A;
fclName = "country, state, region,...";
fcode = ADMD;
fcodeName = "administrative division";
geonameId = 4929431;
lat = "42.2000939";
lng = "-71.8495163";
name = "Town of Auburn";
population = 0;
score = "53.40083694458008";
timezone = {
dstOffset = "-4";
gmtOffset = "-5";
timeZoneId = "America/New_York";
};
toponymName = "Town of Auburn";
},
What I want to do is if the "fcl" (seen in the array) is equal to P, then I want it to show that in the table view. If the "fcl" is some other character, then I don't want it to be seen in the table view. I'm pretty sure that an if statement can do that, but I don't know how to get it so that it filters part of it.
Any help would be appreciated! Thanks
EDIT: As of now, this is the code to search:
- (void)delayedSearch:(NSString*)searchString
{
[self.geoNamesSearch cancel];
[self.geoNamesSearch search:searchString
maxRows:20
startRow:0
language:nil];
}
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
self.searchDisplayController.searchBar.prompt = NSLocalizedStringFromTable(#"ILGEONAMES_SEARCHING", #"ILGeoNames", #"");
[self.searchResults removeAllObjects];
// Delay the search 1 second to minimize outstanding requests
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[self performSelector:#selector(delayedSearch:) withObject:searchString afterDelay:0];
return YES;
}
Your question is basically, how do you filter your array from a search bar string? If so, you can detect when the text changes via UISearchBarDelegate and then go through your array copying those objects that contain the string you are looking for, i.e.
This is the delegate method you want: searchBar:textDidChange:.
[filterArray removeAllObjects];
for(int i = 0; i < [normalArray count]; i++){
NSRange textRange;
textRange =[[[[normalArray objectAtIndex:i] objectForKey:#"name"] lowercaseString] rangeOfString:[searchBarString lowercaseString]];
//I wasn't sure which objectForKey: string you were looking for, just replace the one you want to filter.
if(textRange.location != NSNotFound)
{
[filterArray addObject:[normalArray objectAtIndex:i]];
}
}
filterTableView = YES;
[tableView reloadData];
Note the filterTableView bool value, this is so your tableView knows either to load normally or the filtered version you just made. You implement this in:
tableView:numberOfRowsInSection: //For number of rows.
tableView:cellForRowAtIndexPath: //For the content of the cells.
Hope this is what you were looking for.
NSMutableArray* filtered = [[NSMutableArray alloc] autorelease];
for (int i=0;i<[data count];i++)
{
NSDictionary* item=[data objectAtIndex:i];
if (#"P" == [item objectForKey:#"fcl"] )
{
[filtered addObject:item];
}
}
So every time the search field changes, you will compute a new array, and then reload your tableview. The number of rows will be the numbers of rows in your filtered array.
To compute the new array, you can do this (assuming an array of dictionaries):
NSString *searchString; // from the search field
NSMutableArray *array = [NSMutableArray arrayWithCapacity:[origArray count]];
for(NSDictionary *dict in origArray) {
NSString *val = [dict objectForKey:#"fcl"];
if([val length] >= searchString) {
NSString subString = [val substringToIndex:[searchString length]];
if([subString isEqualToString:val]) [array addObject:dict];
}
}
Each cell then will get its values from the new array.
Just put your json in a NSDictionary and simply do something like :
if ([[yourJson objectForKey:#"fcl"] stringValue] == #"A")
//doSomething

App crashes when using allKeys, is it because there is only one object?

I can't figure out why xcode continues to throw this error
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-
[__NSCFString allKeys]: unrecognized selector sent to instance 0x9367330'
whenever I use this code
NSDictionary *nextArray = [_classDictionary
valueForKeyPath:#"response.page_records.page_record"];
for (NSDictionary *roster2 in nextArray) {
NSArray *keys = [roster2 allKeys];
if ([keys containsObject:#"title"]){
[pageName addObject:[roster2 objectForKey:#"title"]];
}
if ([keys containsObject:#"id"]) {
[pageID addObject:[roster2 objectForKey:#"id"]];
}
}
After trying to debug it I found out that the exception is only thrown when there is only one object for the key. For example when "nextArray" is this:
(
{
"class_id" = 0001;
"id" = 1234;
"title" = "First Page";
},
{
"class_id" = 0002;
"id" = 12345;
"title" = Assignments;
}
)
the code runs without an exception, but when the code is simply
{
"class_id" = 0001;
"id" = 1234;
"title" = "First Page";
}
The exception is thrown. Why would this be happening? The keys still contains the object title, even if there is only one object. Any other ideas why this exception is being thrown? Does it have something to do with the parenthesis? (total guess) Is there a better way to debug what is really going on?
EDIT: Here is the _userDictionary. What is the best way to have an ordered array of the objectforKey: #"title" and avoid the above error?
response = {
"status" = ok;
"page_records" = {
"page" = 1;
"page_count" = 1;
"records_per_page" = 50;
"total_record_count" = 2;
"page_record" = (
{
"class_id" = 0001;
"id" = 1234;
"title" = "Test page";
},
{
"class_id" = 0002;
"id" = 12345;
"title" = "testing page 2";
}
);
};
};
}
In your first example, you are iterating through an array of dictionaries, and hence, NSArray *keys = [roster2 allKeys]; is being called upon an array. However, in your second example, you have just one dictionary. It is not an array of dictionaries. So your loop for (NSDictionary *roster2 in nextArray) is iterating through the keys of your dictionary, which are just strings, and not arrays. Hence, the error.
First of all, your naming is confusing. An NSDictionary named nextArray? Try this:
id *response = [_classDictionary valueForKeyPath:#"response.page_records.page_record"];
NSMutableArray *records;
if ([response isKindOfClass: [NSDictionary class]]) {
records = [[NSMutableArray alloc] init];
[records addObject: response];
}
else {
records = [NSMutableArray arrayWithArray: response];
}
for (NSDictionary *roster2 in records) {
NSArray *keys = [roster2 allKeys];
if ([keys containsObject:#"title"]){
[pageName addObject:[roster2 objectForKey:#"title"]];
}
if ([keys containsObject:#"id"]) {
[pageID addObject:[roster2 objectForKey:#"id"]];
}
}
The error message contains everything you need to know:
[__NSCFString allKeys]: unrecognized selector
It means that you're trying to call the method allKeys on an instance of NSString.
Let's look at where you call allKeys:
for (NSDictionary *roster2 in nextArray) {
NSArray *keys = [roster2 allKeys];
roster2 is an instance of NSString, not NSDictionary. You're expecting nextArray to contain NSDictionary objects, but it instead contains NSString objects.
No, it's because NSString doesn't respond to allKeys. The console log tells you this--you need to read it more carefully.
In NeXT-style plists, ({}) defines an array containing a dictionary. {} defines just a dictionary. That is, this:
{
"class_id" = 0001;
"id" = 1234;
"title" = "First Page";
}
Is not structurally the same as this:
(
{
"class_id" = 0001;
"id" = 1234;
"title" = "First Page";
}
)
Even though they contain the same information. Even if you have only one element in your root array, you still need to tell the plist parser that there is an array there, so you need to include ( and ) for your logic to handle it correctly.
Edit: Given the information in your comment, the solution will not be quite so simple. If your source data is arbitrary that way, you'll need to check the type of the data to see if it's an NSArray or NSDictionary, and handle it appropriately. For example:
id myData = [self plistFromArbitrarySource];
if (myData) {
if ([myData isKindOfClass: [NSArray class]]) {
for (NSDictionary *dict in myData) {
[self parseDictionary:dict];
}
} else if ([myData isKindOfClass: [NSDictionary class]]) {
[self parseDictionary:myData];
}
}

sorting array of nested NSDictionary containing an array

Im quite new to iOS and objective.. heres my question..
if my array looks like this:
myArray = {
parentdict = {
childdict = {
aname = "Aname";
bname - "Bname";
cname = "Cname";
};
childarray = {
{
counter = "1";
close = "25236";
},
{
counter = "2";
close = "12458";
};
};
};
},
{
parentdict = {
childdict = {
aname = "Aname";
bname - "Bname";
cname = "Cname";
};
childarray = {
{
counter = "1";
close = "28556";
},
{
counter = "2";
close = "12118";
};
};
};
},
{
parentdict = {
childdict = {
aname = "Aname";
bname - "Bname";
cname = "Cname";
};
childarray = {
{
counter = "1";
close = "24356";
},
{
counter = "2";
close = "155628";
};
};
};
};
basically its an array of nested dictionary and inside one of the dictionary contains an array of dictionary (childarray) if i want to sort myArray by #"close" of array index 1, which is the one next to counter 2, exactly how should i do this?..(perhaps i should use NSSortDescriptor?)
thanks for the reply
You have presented your structure in the JSON format. I assume it is for sake of presenting it to the audience here. If you are actually starting with JSON string, you will have to convert it to nested NSArrayies and NSDictionaryies using some third-party libs or iOS 5 built-in classes..
Assuming you already have your top level NSArray* myArray, give it a try to the following code:
NSArray* myArray = // ... this is your array
NSArray* sorted_array = [myArray sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
NSDictionary* left = obj1;
NSDictionary* right = obj2;
NSDictionary* left_parent_dict = [left objectForKey: #"parentdict"];
NSDictionary* right_parent_dict = [right objectForKey: #"parentdict"];
NSArray* left_child_array = [left_parent_dict objectForKey: #"childarray"];
NSArray* right_child_array = [right_parent_dict objectForKey: #"childarray"];
NSDictionary* left_child_first = [left_child_array objectAtIndex: 1];
NSDictionary* right_child_first = [right_child_array objectAtIndex: 1];
NSString* left_close = [left_child_first objectForKey: #"close"];
NSString* right_close = [right_child_first objectForKey: #"close"];
NSNumber* left_val = [NSNumber numberWithInt: [left_close intValue]];
NSNumber* right_val = [NSNumber numberWithInt: [right_close intValue]];
return [left_val compare: right_val];
} ];
You will need, of course, to add some checks which I omitted for simplicity.
If you want to get descending order modify the last statement to:
return [right_val compare: left_val];
I would also suggest you considering SQL database for complex data structures as pointed out by Niko.
Sorting an array can be quite complex, especially when the array contains nested objects. May be you should create an SQLite database
You should use iOS 5.0's built in JSON API. [Here's a link to a great tutorial.] (http://www.raywenderlich.com/5492/working-with-json-in-ios-5). JSON is really easy to work with. And that site also has great tutorials on Core Data (iOS's SQLite API) here. There are two more parts to that tutorial as well.
Try this
[array sortUsingComparator:(NSComparator)^(id obj1, id obj2){
int firstValue = [[obj1 objectForKey:#"someKey"] intValue];
int secondValue = [[obj2 objectForKey:#"someKey"] intValue];
int valueDiff = firstValue - secondValue;
return (valueDiff == 0) ? NSOrderedSame : (valueDiff < 0) ? NSOrderedAscending : NSOrderedDescending;
}];

How I can iterate and get all values in a NSDictionary?

I have a problem.
I am using the XMLReader class to get a NSDictionary and everything works fine. But i can't to get the values of the atributes of my productData element.
Specifically, I have the following NSDictionary:
{
response = {
products = {
productsData = (
{
alias = "Product 1";
id = 01;
price = "10";
},
{
alias = "Product 2";
id = 02;
price = "20";
},
{
alias = "Product 3";
id = 03;
price = "30";
});
};
};
}
I used this code to create de NSDictionary:
NSDictionary *dictionary = [XMLReader dictionaryForXMLData:responseData error:&parseError];
and responseData contains:
<application>
<products>
<productData>
<id>01</id>
<price>10</price>
<alias>Product 1</alias>
</productData>
<productData>
<id>02</id>
<price>20</price>
<alias>Product 2</alias>
</productData>
<productData>
<id>02</id>
<price>20</price>
<alias>Product 3</alias>
</productData>
</products>
</application>
Then, i don't know how get the values of each productData like id, price and alias...
Anybody know how to do it??
Thanks and please forgive my bad english !
NSArray* keys = [theDict allKeys];
for(NSString* key in keys) {
id obj = [theDict objectForKey:key];
// do what needed with obj
}
you could try something like this :
NSArray* keys = [theDict allKeys];
for(NSString* key in keys) {
if ([key isEqualToString:#"product"]) {
NSArray* arr = [theDict objectForKey:key];
// do what needed with arr
}
}
There is a method on NSDictionary - -allValueswhich returns a new array containing the dictionary’s values. Maybe that will help.
Starting with
NSDictionary *dictionary = [XMLReader dictionaryForXMLData:responseData error:&parseError];
you can do something like this:
NSDictionary *application = [dictionary objectForKey:#"application"];
if ([[application objectForKey:#"products"] isKindOfClass [NSArray class]]) {
NSArray *products = [application objectForKey:#"products"];
for (NSDictionary *aProduct in products) {
// do something with the contents of the aProduct dictionary
}
else if {[[application objectForKey:#"products"] isKindOfClass [NSDictionary class]]) {
// you will have to see what the returned results look like if there is only one product
// that is not in an array, but something along these lines may be necessary
// to get to a single product dictionary that is returned
}
I have had a case similar to this (parsing JSON) where an array was not returned for a signle value, so the result had to be checked for both an array (of product dictionaries in your case) or a single NSDictionary (the product dictionary in your case).