JSON Parsing with NSDictionary and finding value by key - ios

I have a simple NSDictionary that I am trying to populate with data from an external site via JSON that is returned. The JSON that is returned is fine but I am haveing trouble getting actual data for a specific key.
Here is the JSON data printed to the console.
This is my JSON data:
(
{
CategoryID = 12345;
CategoryName = "Baked Goods";
},
{
CategoryID = 12346;
CategoryName = Beverages;
},
{
CategoryID = 12347;
CategoryName = "Dried Goods";
},
{
CategoryID = 12348;
CategoryName = "Frozen Fruit & Vegetables";
},
{
CategoryID = 12349;
CategoryName = Fruit;
},
{
CategoryID = 12340;
CategoryName = "Purees & Soups";
},
{
CategoryID = 12341;
CategoryName = Salad;
},
{
CategoryID = 12342;
CategoryName = "Snack Items";
},
{
CategoryID = 12343;
CategoryName = Vegetables;
}
)
The error I am getting is:
Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: '-[__NSCFArray
enumerateKeysAndObjectsUsingBlock:]: unrecognized selector sent to
instance 0x6884000'
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSError *error = nil;
// Get the JSON data from the website
NSDictionary *categories = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
if (categories.count > 0){
NSLog(#"This is my JSON data %#", categories);
[categories enumerateKeysAndObjectsUsingBlock: ^(__strong id key, __strong id obj, BOOL *stop) {
NSLog(#"Key = %#, Object For Key = %#", key, obj); }];
}
I'm not sure why this is happening but I'm sure it's something simple like I am using the incorrect object or something.
Help is appreciated.

+JSONObjectWithData:options:error: is returning an NSArray not an NSDictionary. '-[__NSCFArray enumerateKeysAndObjectsUsingBlock:] is the key part of the error message. It tells you that you are calling -enumerateKeysAndObjectsUsingBlock: on an array.
For this case, you could use -enumerateObjectsUsingBlock: instead.
If you are not sure wether a NSArray or an NSDictionary will be returned, you can use -isKindOf:
id result = [NSJSONSerialization …];
if ([result isKindOf:[NSArray class]]) {
NSArray *categories = result;
// Process the array
} else if ([result isKindOf:[NSDictionary class]]) {
NSDictionary *categories = result;
// Process the dictionary
}
From enumerateObjectsUsingBlock:
Executes a given block using each object in the array, starting with the first object and continuing through the array to the last object.
(void)enumerateObjectsUsingBlock:(void (^)(id obj, NSUInteger idx, BOOL *stop))block
So it should be called as such
[categories enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSLog(#"index = %d, Object For Key = %#", idx, obj);
}];
A quick read of the documentation really can save you lots of frustration.

Related

Combine an array of objects for the same value in Objective-C iOS

I have an array of custom object name finalBarListArray with entity BarCodeSKULists (refer to the attachment 1) which contains sales_sub_category_name (string type), count (int type) and an array of another custom object name arrayBarCodeSKUList (BarCodeSKUList) (refer to the attachment 2/3/4).
Please see the below screenshots:
Now I need to create an array and have to check if there will be same sales_sub_category_name value then need to add their arrayBarCodeSKUList in one.
Like in above array finalBarListArray, there are three elements with sales_sub_category_name 'ENVY 17', 'ENVY 15' & 'ENVY 15', so I need to merge second and third element into one for second index of array. It means Output should have only two elements, where first element should have (count = 1 & one arrayBarCodeSKUList) and the second element should have (count = 2 & two arrayBarCodeSKUList).
Use the following method to merge the array of custom objects, which has same sales_sub_category_names.
- (NSArray *)mergeDuplicate {
NSMutableDictionary *mergedDictionary = [[NSMutableDictionary alloc]init];
// Use sales_sub_category_name value as a key for the dictioanry.
[finalBarListArray enumerateObjectsUsingBlock:^(BarCodeSKULists * _Nonnull object, NSUInteger idx, BOOL * _Nonnull stop) {
id existingItem = [mergedDictionary valueForKey:object.sales_sub_category_name];
if (existingItem) {
// If object exist then check that type is NSMutableArray or not
if ([existingItem isKindOfClass:[NSMutableArray class]]) {
// If yes then append with existing array
[existingItem addObject:object];
mergedDictionary[object.sales_sub_category_name] = existingItem;
} else if ([existingItem isKindOfClass:[BarCodeSKULists class]]) {
// Else if the object is `BarCodeSKULists ` class then create array and added previous item and current item into one array
NSMutableArray *itemList = [NSMutableArray arrayWithObjects:existingItem, object, nil];
mergedDictionary[object.sales_sub_category_name] = itemList;
}
} else {
// If it is first time then add it to the dictionary
mergedDictionary[object.sales_sub_category_name] = object;
}
}];
NSLog(#"%#", mergedDictionary.allValues);
return mergedDictionary.allValues;
}
mergedDictionary.allValues will give the expected array of items
Update :
As per the discussion.
- (NSArray *)mergeDuplicate:(NSMutableArray *) list{
NSMutableDictionary *mergedDictionary = [[NSMutableDictionary alloc]init];
// Use sales_sub_category_name value as a key for the dictioanry.
[list enumerateObjectsUsingBlock:^(BarCodeSKUList * _Nonnull object, NSUInteger idx, BOOL * _Nonnull stop) {
BarCodeSKUList *existingItem = [mergedDictionary valueForKey:object.sales_sub_category_name];
if (existingItem) {
[existingItem.arrayBarCodeSKUList addObjectsFromArray:object.arrayBarCodeSKUList];
mergedDictionary[object.sales_sub_category_name] = existingItem;
} else {
// If it is first time then add it to the dictionary
mergedDictionary[object.sales_sub_category_name] = object;
}
}];
return mergedDictionary.allValues;
}
- (NSArray *)mergeObject{
NSMutableDictionary *dic = [NSMutableDictionary new];
[_originArray enumerateObjectsUsingBlock:^(BarCodeSKULists* _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
id item = [dic objectForKey:obj.sales_sub_category_name];
if (item) {
BarCodeSKULists *list = (BarCodeSKULists *)item;
[list.arrayBarCodeSKUList addObject:obj.BarCodeSKUList];
list.count = (int)list.arrayBarCodeSKUList.count;
dic[obj.sales_sub_category_name] = list;
}
else{
dic[obj.sales_sub_category_name] = obj;
}
}];
return dic.allValues;
}

Find index of value which is stored into NSDictionary and NSDictionary stored into NSMutableArray

I have NSMutableArray which stores NSDictionary. Consider following array which contain NSDictionary.
<__NSArrayM 0x7f9614847e60>(
{
"PARAMETER_KEY" = 1;
"PARAMETER_VALUE" = ALL;
},
{
"PARAMETER_KEY" = 2;
"PARAMETER_VALUE" = ABC;
},
{
"PARAMETER_KEY" = 3;
"PARAMETER_VALUE" = DEF;
},
{
"PARAMETER_KEY" = 4;
"PARAMETER_VALUE" = GHI;
},
{
"PARAMETER_KEY" = 5;
"PARAMETER_VALUE" = JKL;
}
)
I can find index of specific NSDictionary using following code.
int tag = (int)[listArray indexOfObject:dictionary];
But If I have PARAMETER_VALUE = GHI and using this value I want to find that dictionary index into array. I don't want to use for loop. Can I get index without for loop?
You can use indexOfObjectPassingTest method of NSArray:
[listArray indexOfObjectPassingTest:^BOOL(NSDictionary* _Nonnull dic, NSUInteger idx, BOOL * _Nonnull stop) {
return [dic[#"PARAMETER_VALUE"] isEqualToString:#"GHI"];
}];
Also, please consider using indexesOfObjectsPassingTest if you can have multiple dictionaries with the same PARAMETER_VALUE
You can add a category on NSArray like this (this does a type safety check as well; only array of dictionaries are processed):
- (NSInteger)indexOfDictionaryWithKey:(NSString *)iKey andValue:(id)iValue {
NSUInteger index = [self indexOfObjectPassingTest:^BOOL(NSDictionary *dict, NSUInteger idx, BOOL *stop) {
if (![dict isKindOfClass:[NSDictionary class]]) {
*stop = YES;
return false;
}
return [dict[iKey] isEqual:iValue];
}];
return index;
}
And then simply call indexOfDictionaryWithKey:andValue: directly on your array object to get the index.
Just in case if you want to get the dictionary object out of that array, add one more category in NSArray:
- (NSDictionary *)dictionaryWithKey:(NSString *)iKey andValue:(id)iValue {
NSUInteger index = [self indexOfDictionaryWithKey:iKey andValue:iValue];
return (index == NSNotFound) ? nil : self[index];
}
You can use NSPredicate for this purpose:
// Creating predicate
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.PARAMETER_VALUE MATCHES %#",#"GHI"];
// Filtering array
NSArray *filteredArr = [arr filteredArrayUsingPredicate:predicate];
// If filtered array count is greater than zero (that means specified object is available in the array), checking the index of object
// There can be multiple objects available in the filtered array based on the value it holds (In this sample code, only checking the index of first object
if ([filteredArr count])
{
NSLog(#"Index %d",[arr indexOfObject:filteredArr[0]]);
}
Well, one has to enumerate in a way. Taking your requirement literally (no for loop), you can use fast enumeration. However, the task can be run concurrently, because you only need read access:
__block NSUInteger index;
[array enumerateObjectsWithOptions: NSEnumerationConcurrent
usingBlock:
^(NSDictionary *obj, NSUInteger idx, BOOL *stop)
{
if( [obj valueForKey:#"PARAMETER_VALUE" isEqualToString:#"GHI" )
{
index = idx;
*stop=YES;
}
}

Parsing JSON using objective - c

Im getting a JSON as below from a web service
data = {
following = 1;
};
message = "You are now following";
status = 1;
and I am trying to loop it using the following code (in order to get the value of the "following" key)
for(NSDictionary *item in datarecieved){
int placeName = [item valueForKey:#"following"];
NSLog(#"FOLLOWING VALUE %i", placeName);
}
But I am getting an exception - "uncaught exception of type NSException"
You have to do like this
for(NSDictionary *item in datarecieved){
if([item class] == [NSDictionary class])
{
int placeName = [item valueForKey:#"following"];
NSLog(#"FOLLOWING VALUE %i", placeName);
}
}
because there are two other key in response and they dosen't contain following key
Try it
int placeName = [datarecieved[#"data"][#"following"] intValue];
Once you deserialize the JSON it will be NSDictionary, so you don't need to use a loop (unless you need to loop through all keys)
In your case if you just want value for following key, you can do the following
NSNumber *following = [datarecieved valueForKeyPath:#"data.following"];
NSLog(#"FOLLOWING VALUE %#", following);
First of all, you need valid JSON. Look into the OP comments, there is a good address for understanding JSON.
Then you first must parse the JSON data. Assuming datareceived is of class NSData:
NSError *error;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:datareceived options:nil error:&error];
if(error) {
NSLog(#"parsing error: %#", error);
}
else {
for(NSString *key in [dict allKeys]) {
NSLog(#"%# = %#", key, [dict objectForKey:key]);
}
}
The exception you are doubtless getting is unrecognized selector sent to instance 0x12345678 and is because item does not support that selector.
You need to check that item is actually an NSDictionary before calling valueForKey: (you should be using objectForKey: anyway):
for(NSDictionary *item in datarecieved){
if ([item isKindOfClass:[NSDictionary class]]) {
int placeName = [item objectForKey:#"following"];
NSLog(#"FOLLOWING VALUE %i", placeName);
}
}
(you probably want to break at some point once you've found what you're looking for as well).

-[__NSCFArray objectForKeyedSubscript:] error?

I'm trying to get the data from a JSON response object in my iOS app after I log in. I keep getting this error though.
Error:
'NSInvalidArgumentException', reason: '-[__NSCFArray objectForKeyedSubscript:]: unrecognized selector sent to instance 0x8fc29b0'
Here is my code for the request, I'm using AFNetworking:
self.operation = [manager GET:urlString parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSDictionary *JSON = (NSDictionary *)responseObject;
NSDictionary *user = JSON[#"user"];
NSString *token = user[#"auth_token"];
NSString *userID = user[#"id"];
// NSString *avatarURL = user[#"avatar_url"];
// weakSelf.credentialStore.avatarURL = avatarURL;
weakSelf.credentialStore.authToken = token;
weakSelf.credentialStore.userId = userID;
weakSelf.credentialStore.username = self.usernameField.text;
weakSelf.credentialStore.password = self.passwordField.text;
[SVProgressHUD dismiss];
[self dismissViewControllerAnimated:YES completion:nil];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (operation.isCancelled) {
return;
}
[SVProgressHUD showErrorWithStatus:#"Login Failed"];
NSLog(#"%#", error);
}];
What the JSON response object looks like logged:
<__NSCFArray 0x8cac0b0>(
{
user = {
"auth_token" = b3a18e0fb278739649a23f0ae325fee1e29fe5d6;
email = "jack#jack.com";
id = 1;
username = jack;
};
}
)
I'm converting the array to a Dictionary using pointers like this:
EDIT: As pointed out in the comments incase anyone else stumbles across this with limited knowledge in iOS. I'm casting here not converting. See the answers for a full explanation.
NSDictionary *JSON = (NSDictionary *)responseObject;
I'm new to iOS, apologies if problem is obvious.
Thanks for any help.
The "conversion" you do is not doing any conversion, it's a cast. This simply tells the compiler to ignore the type it knows for this object and act as if it's the type you pass it.
Looking at the output you have, you don't get a dictionary back, but an array of dictionaries with a single dictionary. To get to the first dictionary you can use this instead of the cast:
NSDictionary *JSON = [responseObject objectAtIndex:0];
Note that since you get your data from a web service, you should probably also check if the contents you get are what you expect.
You say:
I'm converting the array to a Dictionary using pointers like this:
But that is not what you are doing. You are casting it, but the underlying object is still an array.
From the JSON response you can see that those JSON is constructed as an array with a single element which is a dictionary. You can get to the dictionary by calling [responseObject firstObject];. Of course, so that you don't get error going in the other direction, you should check how the input is constructed before calling any array or dictionary specific methods on the response object.
You have to convert your self but do not use casting.
Or, this is the code to detect if a json object is an array or dictionary
NSError *jsonError = nil;
id jsonObject = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:&jsonError];
if ([jsonObject isKindOfClass:[NSArray class]]) {
NSLog(#"its an array!");
NSArray *jsonArray = (NSArray *)jsonObject;
NSLog(#"jsonArray - %#",jsonArray);
}
else {
NSLog(#"its probably a dictionary");
NSDictionary *jsonDictionary = (NSDictionary *)jsonObject;
NSLog(#"jsonDictionary - %#",jsonDictionary);
}

Getting Terminating app due to uncaught exception 'NSInvalidArgumentException' accessing a web api

I am trying to consume a simple web api that returns the data below in Objective C using AFJSONRequestOperation.
Results; (
{
Category = Groceries;
Id = 1;
Name = "Tomato Soup";
Price = 1;
},
{
Category = Toys;
Id = 2;
Name = "Yo-yo";
Price = "3.75";
},
{
Category = Hardware;
Id = 3;
Name = Hammer;
Price = "16.99";
}
)
My Objective-C call looks like this:
//not the real URL, just put in to show the variable being set
NSURL *url = [NSURL URLWithString:#"http://someapi"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation;
operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
success: ^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSLog(#"Results; %#", JSON);
self.resultsArray= [JSON objectForKey:#"Results"];
}
failure: ^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error ,id JSON) {
//http status code
NSLog(#"Received Error: %d", response.statusCode);
NSLog(#"Error is: %#", error);
}
];
//run service
[operation start];
When I run my code, I can see the data returned in the NSLog statement. However I get the following error when I try to set the results to my array using the JSON objectForKey statement.
-[__NSCFArray objectForKey:]: unrecognized selector sent to instance 0xe466510
2013-09-30 20:49:03.893 ITPMessageViewer[97459:a0b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFArray objectForKey:]: unrecognized selector sent to instance 0xe466510'
I'm fairly new to Objective-C, and can't figure out why this isn't working. Any help or ideas would be appreciated.
The result you are getting is an Array
objectForKey: is a NSDictionary method
So use valueForKey: which is a NSArray method.
self.resultsArray= [JSON valueForKey:#"Results"];
In your code, the JSON ( result ) need to follow this method to use :
self.resultsArray = (NSArray *)JSON;
It'll force the " id " type to NSArray for your self.resultsArray type ( It is an NSArray, right ? ), then you can use this method to enumerate it.
[self.resultsArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop)
{
NSDictionary *_eachResult = (NSDictionary *)obj;
[_eachResult objectForKey:#"Category"];
//...
}];
Hope it can help you.
I managed to update my web service to return data in a format that the AFJSONRequestOperation was happy with. This allowed me to get the JSON to parse properly. Still not sure why it needed this dictionary combo, but glad it worked.
In case anyone is writing a web api in C# to talk to objective C, this is what I did:
The update was to return the following object as JSON (code is in C#).
Dictionary > with string = "results" and the IEnumerable is my strongly typed array of objects.
public Dictionary<string, IEnumerable<Product>> GetAllProducts()
{
var sortedProuct = results.OrderBy(a => a.Category);
var proddict = new Dictionary<string, IEnumerable<Product>>()
{
{ "results", results},
};
return proddict;
}
this translates to:
Results; {
results = ({
Category = Groceries;
Id = 1;
Name = "Tomato Soup";
Price = 1;},
....

Resources