NSdictionary returns error when 1 returns - objective-c

Everything works when i get more then 1 objects back but when its only 1 it reacts weird, i can't find the solution for it.
First i set everything in an array:
NSArray *array = [[[dictionary objectForKey:#"Response"] objectForKey:#"objecten"] objectForKey:#"object"];
if (array == nil) {
NSLog(#"Expected 'results' array");
return;
}
then i use a for loop on a dictionary
for (NSDictionary *resultDict in array) {
SearchResult *searchResult;
NSString *wrapperType = [resultDict objectForKey:#"type"];
if ([wrapperType isEqualToString:#"rent"])
{
searchResult = [self parseHuur:resultDict];
}
if (searchResult != nil) {
[searchResults addObject:searchResult];
}}
So when results get back more then 1 everything works great, but when just one gets back i get:
-[__NSCFString objectForKey:]: unrecognized selector sent to instance 0x6e52c30
*** Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: '-[__NSCFString objectForKey:]:
unrecognized selector sent to instance 0x6e52c30'
it points to this line:
NSString *wrapperType = [resultDict objectForKey:#"type"];
I really don't get it...
i check the results of the api in the browser with the same link and it really returns 1 object, but when i log the resultDict (NSlog it) i get only one answer: id instead of the whole object with all parameters (i don't know if this is the right name for it)
how can that be ?

Some of your results aren't full NSDictionaries but rather just NSStrings. You can check for this:
for (id result in array) {
if ([result isKindOfClass:[NSDictionary class]]) {
NSDictionary *resultDict = (NSDictionary *)result;
...

As per your comments, array is not always an array as you have mentioned. It could be an array or dictionary. So try this,
id someObject = [[[dictionary objectForKey:#"Response"] objectForKey:#"objecten"] objectForKey:#"object"]; //naming it as someObject since it is not always an array
if (someObject == nil) {
NSLog(#"Expected 'results' array");
return;
}
if ([someObject isKindOfClass:[NSDictionary class]]) { //Just add this
someObject = [NSArray arrayWithObject:someObject];
}
NSArray *array = (NSArray *)someObject;//type cast to an array now
for (NSDictionary *resultDict in array) {
SearchResult *searchResult;
NSString *wrapperType = [resultDict objectForKey:#"type"];
if ([wrapperType isEqualToString:#"rent"])
{
searchResult = [self parseHuur:resultDict];
}
if (searchResult != nil) {
[searchResults addObject:searchResult];
}
}

When you use the fast enumeration for a NSDictionary, the iterating variable is from the set of keys in the dictionary not the values.
http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/Chapters/ocFastEnumeration.html

The resultDict isn't an NSDictionary hence you can't invoke objectForKey: on this object.
A better solution would be to treat resultDict as id type in the for loop, and check its class type for NSDictionary before using it.

-[__NSCFString objectForKey:]
So it's calling the objectForKey: method on an NSString. It seems that the API you're using for getting the objects follows a common idiom: it uses duck-typing/polimorphism (to use these nice OO-related words) and it returns either an array of objects if it has more than results, or a single object (and not an array of one element) when it has only one result. So, you have to use reflection (OMG, even more OO terminology!) to inspect whether the returned object is actually an array - either
id result = [dictionary objectForKey:#"Response"];
id value;
if ([result isKindOfClass:[NSDictionary class]]) {
value = [[result object objectForKey:#"objecten"] objectForKey:#"object"];
} else {
value = result;
}
or
id value;
if ([dictionary isKindOfClass:[NSDictionary class]]) {
value = [[[dictionary objectForKey:#"Result"] result object objectForKey:#"objecten"] objectForKey:#"object"];
} else {
value = dictionary;
}
Try both, whichever works should be fine.

Related

AFNetworking [NSNull length]

I'm getting a crash in my app with this error:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSNull length]: unrecognized selector sent to instance 0x1cb8068'
I have a lot of data I need to display in my table.
In this example, I am working with the key, brands, in my JSON.
If I NSLog the brands I will get something like this:
Brand A
Brand B
Brand C
null
Brand E
null
null
Brand Z
When I scroll through my table and I hit a the app crashes.
How can I replace the with a string?
Here's my method:
- (void)updateData
{
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:#"http://mydataurl.com/abc" parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSArray * aArray = [responseObject valueForKeyPath:#"result_payload.items"];
self.inventoryArray = [NSArray arrayWithArray:aArray];
// ***** Brand ******
self.brandArray = [self.inventoryArray valueForKeyPath:#"brand"];
for (int a = 0; a < [self.brandArray count]; a++)
{
self.brandString = [self.brandArray objectAtIndex:a];
}
[self.tableView reloadData];
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
I have added a category for NSDictionary:
.h
#import <Foundation/Foundation.h>
#interface NSDictionary (Utility)
- (NSString*)stringForKey:(id)key;
#end
.m
#import "NSDictionary+Utility.h"
#implementation NSDictionary (Utility)
- (NSString*)stringForKey:(id)key
{
NSString * string = [self objectForKey:key];
if ([string isEqual:[NSNull null]])
{
return nil;
}
return string;
}
#end
I haven't used categories much, do I need to call this method in a specific place? Or just add the #import to my main class?
Are you sure the JSON is not returning an element with a "null" value?
If you look at the actual JSON document that the server gives you, you will find that it contains "null" values. When there is a "null" value in the data, JSON parsers will produce the value [NSNull null] to represent the null.
You have to check for that value. If you think you've got an NSString, or an NSNumber, or an NSArray or an NSDictionary but what you really have is an [NSNull null], and you send it a message, you will get a crash.
The easiest way to handle this: Add a category to NSDictionary with methods like
(NSString*)stringForKey;
(NSNumber*)numberForKey;
which calls objectForKey, checks the type of the object, and returns nil if its the wrong type.
As an example:
#interface NSDictionary (JSONExtensions)
- (NSString*)stringForKey:(NSString*)key;
#end
#implementation NSDictionary (JSONExtensions)
- (NSString*)stringForKey:(NSString *)key
{
id result = [self objectForKey:key];
if ([result isKindOfClass:[NSNumber class]])
result = [((NSNumber *) result) stringValue];
else if (! [result isKindOfClass:[NSString class]])
result = nil;
return result;
}
#end
This will return nil if the key is not present, or if the value is [NSNull null], or if the value is an array or a dictionary. It will convert a number to a string and return a string, so you are guaranteed to get nil or an NSString*. (Some servers tend to send strings consisting only of digits as numbers instead of strings). So if you want a string for the key "MyString", you write
NSString* myString = [myDict stringForKey:#"MyString"];
and you can be sure the result is nil or a string.
I use
- (id)objectForKeyNotNull:(id)key
{
id object = [self objectForKey:key];
if (object == [NSNull null])
return nil;
return object;
}
to convert the NSNull values to nil. I add this as a category onto NSDictionary.

ios parse jsonresult how to iterate if it's not a dictionary?

I have this issue when I'm trying to parse my jsonresult that comes from a webapi. Take note, that whenver it would return a json result that looks like this:
[{"Id":0, "Name":Wombat, "Category":Animal},
{"Id":1, "Name":Trident, "Category":Object}]
This code works on that result:
NSArray *jsonresult = [WCFServiceRequest processWebApiGETRequestWithURL:url];
if(jsonresult){
for(id item in jsonresult){
NSLog(#"item is %#", item);
if([item isKindOfClass:[NSDictionary class]]){
object.Id = [item objectForKey:#"Id"];
object.name = [item objectForKey:#"Name"];
object.category = [item objectForKey:#"Category"];
}
}
}
But once it returns a none list result that looks like so:
{"Id":1, "Name":Trident, "Category":Object}
It would not go through the
if([item isKindOfClass:[NSDictionary class]]){
Now if I take it out and just let it go straight to the assignment of the properties. The "item" variable that is returned from the jsonarray is the key eg: Id, Name, etc. Now I'm not sure how to properly iterate through the thing and assign it using keys. It seems like it's using indexes? Do I make another dictionary?
Its quite simple to get the solution of it, when you are unaware about the result DataType simply TypeCast your object to id (A Generic DataType)
Store your json Response in id
id jsonresult = [WCFServiceRequest processWebApiGETRequestWithURL:url];
// Check whether Response is An Array of Dictionary
if([jsonresult isKindOfClass:[NSArray class]])
{
NSLog(#"it has multiple Dictionary so iterate through list");
if(jsonresult){
for(id item in jsonresult){
NSLog(#"item is %#", item);
if([item isKindOfClass:[NSDictionary class]]){
object.Id = jsonresult[#"Id"];
object.name = jsonresult[#"Name"];
object.category = jsonresult[#"Category"];
}
}
}
}
// Its Dictionary
else if([jsonresult isKindOfClass:[NSDictionary class]])
{
NSLog(#"It has only one dictionary so simply read it");
object.Id = jsonresult[#"Id"];
object.name = jsonresult[#"Name"];
object.category = jsonresult[#"Category"];
}
Your response is Dictionary when you have only 1 Record in Result, and when you have more then 1 Record it will be an Array.

NSNull handling for NSManagedObject properties values

I'm setting values for properties of my NSManagedObject, these values are coming from a NSDictionary properly serialized from a JSON file. My problem is, that, when some value is [NSNull null], I can't assign directly to the property:
fight.winnerID = [dict objectForKey:#"winner"];
this throws a NSInvalidArgumentException
"winnerID"; desired type = NSString; given type = NSNull; value = <null>;
I could easily check the value for [NSNull null] and assign nil instead:
fight.winnerID = [dict objectForKey:#"winner"] == [NSNull null] ? nil : [dict objectForKey:#"winner"];
But I think this is not elegant and gets messy with lots of properties to set.
Also, this gets harder when dealing with NSNumber properties:
fight.round = [NSNumber numberWithUnsignedInteger:[[dict valueForKey:#"round"] unsignedIntegerValue]]
The NSInvalidArgumentException is now:
[NSNull unsignedIntegerValue]: unrecognized selector sent to instance
In this case I have to treat [dict valueForKey:#"round"] before making an NSUInteger value of it. And the one line solution is gone.
I tried making a #try #catch block, but as soon as the first value is caught, it jumps the whole #try block and the next properties are ignored.
Is there a better way to handle [NSNull null] or perhaps make this entirely different but easier?
It might be a little easier if you wrap this in a macro:
#define NULL_TO_NIL(obj) ({ __typeof__ (obj) __obj = (obj); __obj == [NSNull null] ? nil : obj; })
Then you can write things like
fight.winnerID = NULL_TO_NIL([dict objectForKey:#"winner"]);
Alternatively you can pre-process your dictionary and replace all NSNulls with nil before even trying to stuff it into your managed object.
Ok, I've just woke up this morning with a good solution. What about this:
Serialize the JSON using the option to receive Mutable Arrays and Dictionaries:
NSMutableDictionary *rootDict = [NSJSONSerialization JSONObjectWithData:_receivedData options:NSJSONReadingMutableContainers error:&error];
...
Get a set of keys that have [NSNull null] values from the leafDict:
NSSet *nullSet = [leafDict keysOfEntriesWithOptions:NSEnumerationConcurrent passingTest:^BOOL(id key, id obj, BOOL *stop) {
return [obj isEqual:[NSNull null]] ? YES : NO;
}];
Remove the filtered properties from your Mutable leafDict:
[leafDict removeObjectsForKeys:[nullSet allObjects]];
Now when you call fight.winnerID = [dict objectForKey:#"winner"]; winnerID is automatically going to be (null) or nil as opposed to <null> or [NSNull null].
Not relative to this, but I also noticed that it is better to use a NSNumberFormatter when parsing strings to NSNumber, the way I was doing was getting integerValue from a nil string, this gives me an undesired NSNumber of 0, when I actually wanted it to be nil.
Before:
// when [leafDict valueForKey:#"round"] == nil
fight.round = [NSNumber numberWithInteger:[[leafDict valueForKey:#"round"] integerValue]]
// Result: fight.round = 0
After:
__autoreleasing NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init];
fight.round = [numberFormatter numberFromString:[leafDict valueForKey:#"round"]];
// Result: fight.round = nil
I wrote a couple of category methods to strip nulls from a JSON-generated dictionary or array prior to use:
#implementation NSMutableArray (StripNulls)
- (void)stripNullValues
{
for (int i = [self count] - 1; i >= 0; i--)
{
id value = [self objectAtIndex:i];
if (value == [NSNull null])
{
[self removeObjectAtIndex:i];
}
else if ([value isKindOfClass:[NSArray class]] ||
[value isKindOfClass:[NSDictionary class]])
{
if (![value respondsToSelector:#selector(setObject:forKey:)] &&
![value respondsToSelector:#selector(addObject:)])
{
value = [value mutableCopy];
[self replaceObjectAtIndex:i withObject:value];
}
[value stripNullValues];
}
}
}
#end
#implementation NSMutableDictionary (StripNulls)
- (void)stripNullValues
{
for (NSString *key in [self allKeys])
{
id value = [self objectForKey:key];
if (value == [NSNull null])
{
[self removeObjectForKey:key];
}
else if ([value isKindOfClass:[NSArray class]] ||
[value isKindOfClass:[NSDictionary class]])
{
if (![value respondsToSelector:#selector(setObject:forKey:)] &&
![value respondsToSelector:#selector(addObject:)])
{
value = [value mutableCopy];
[self setObject:value forKey:key];
}
[value stripNullValues];
}
}
}
#end
It would be nice if the standard JSON parsing libs had this behaviour by default - it's almost always preferable to omit null objects than to include them as NSNulls.
Another method is
-[NSObject setValuesForKeysWithDictionary:]
In this scenario you could do
[fight setValuesForKeysWithDictionary:dict];
In the header NSKeyValueCoding.h it defines that "Dictionary entries whose values are NSNull result in -setValue:nil forKey:key messages being sent to the receiver.
The only downside is you will have to transform any keys in the dictionary to keys that are in the receiver. i.e.
dict[#"winnerID"] = dict[#"winner"];
[dict removeObjectForKey:#"winner"];
I was stuck with the same problem, found this post, did it in a slightly different way.Using category only though -
Make a new category file for "NSDictionary" and add this one method -
#implementation NSDictionary (SuperExtras)
- (id)objectForKey_NoNSNULL:(id)aKey
{
id result = [self objectForKey:aKey];
if(result==[NSNull null])
{
return nil;
}
return result;
}
#end
Later on to use it in code, for properties that can have NSNULL in them just use it this way -
newUser.email = [loopdict objectForKey_NoNSNULL:#"email"];
Thats it

How to parse json specific fields using objective-c

I'm trying to parse a very simple json object with 1 value. (ocUnit test below)
- (void) testHatCartParseWithValidRefId {
NSString* data = #"{\"refid\":999}";
Cart* obj = [HatCartParseJson parseJsonAndReturnObject:data];
STAssertTrue([obj.refid isEqualToString:#"999"], #"fail");
}
In the implementation everything fails when I add the line to pull it from either key or index. How should I pull this from the json input? Please keep in mind I need this json parse (not string) the actual code I'm working with is a large set of JSON data.
+ (Cart *) parseJsonAndReturnObject:(NSString *)json
{
NSArray* cart = [json JSONValue];
for (NSDictionary* item in cart) {
Cart* obj = [[Cart alloc] init];
//NSString* refid = [item objectAtIndex:0];
//NSString* refid = [item objectForKey:#"refid"];
[obj setRefid:#"999"];
return obj;
}
return nil;
}
Thank you in advance
You are expecting that the return value of JSONValue is an NSArray, which in this case it isn't.
So, you must do a check if the return value is actually an NSArray, and if it is, then iterate through the collection, otherwise check if it's an NSDictionary, and if it is, then return the Cart object with the refid from the NSDictionary. If all of this fails, then just return nil.
As a side point, according to Apple's Object Ownership Policy, you should return autorelease-d objects from methods whose names do not contain the words "alloc", "new" or "copy". This would be one such method where you'd return an autorelease-d object.
+ (Cart *) parseJsonAndReturnObject:(NSString *)json
{
id cart = [json JSONValue];
NSString* refid = nil;
if([cart isKindOfClass:[NSArray class]]) {
refid = [[cart objectAtIndex:0] objectForKey:#"refid"];
} else if([cart isKindOfClass:[NSDictionary class]]) {
refid = [cart objectForKey:#"refid"];
}
if(refid) {
Cart* c = [[Cart alloc] init];
[c setRefid:refid];
return [c autorelease];
}
return nil;
}

Obj-C determing integer values with isKindOfClass

I need to check the type of each element in an array...
for(id obj in items) {
if([obj isKindOfClass:[NSString class]]) {
//handle string case
} else if([obj isKindOfClass:[NSInteger class]]) { //THIS LINE GIVES ERROR
//handle int case
}
}
Of course NSInteger is just an alias for int, so how can I check for this at runtime?
You can't actually store NSInteger in an NSArray, since it isn't an object. If you are storing numbers in your array, they are most likely instances of NSNumber, so you would check for:
if ([obj isKindOfClass:[NSNumber class]]) { ... }
iPhone Developer Tips gives a good summary of the difference between NSInteger and NSNumber.