If got a NSMutableDictionary from Json Data
NSMutableDictionary *returnedDict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
I know that this key returnedDict[#"data"][#"weather"][day][#"tides"] is NSNull in some cases. So I get -[NSNull objectForKeyedSubscript:]
So I try according to this answer How to check if an NSDictionary or NSMutableDictionary contains a key? to check if it is nil or not.
if (returnedDict[#"data"][#"weather"][day][#"tides"]){ some code }
and
if (returnedDict[#"data"][#"weather"][day][#"tides"]!=[NSNull null]){ some code}
does not avoid to run {some code}
How do I check this in the right way?
So your issue is:
Your server may return null to indicate that an object isn't present. NSJSONSerialization will convert that null into an instance of NSNull. In theory that means that instead of doing result[a][b][c] you need to check whether result[a] is a dictionary and, if so, whether result[a][b] is a dictionary, etc, etc, which is repetitious and error-prone?
Perhaps the easiest thing might be to remove from the dictionary any key with a value of NSNull, so that next time you ask for the value you'll get an ordinary nil, which is safe to message per the usual compound-messaging rules?
NSJSONSerialization won't do that for you but it's easy enough to add after the fact:
#interface NSDictionary(RemoveNullValues)
- (NSDictionary *)ty_collectionWithoutNullValues;
#end
#interface NSArray(RemoveNullValues)
- (NSArray *)ty_collectionWithoutNullValues;
#end
[...]
#implementation NSDictionary(RemoveNullValues)
- (NSDictionary *)ty_collectionWithoutNullValues {
NSMutableDictionary *reducedDictionary = [self mutableCopy];
// remove any keys for which NSNull is the direct value
NSArray *keysEvaluatingToNull = [self allKeysForObject:[NSNull null]];
[reducedDictionary removeObjectsForKeys:keysEvaluatingToNull];
// ask any child dictionaries to do the same; note that it's safe
// to mutate reducedDictionary in this array because allKeys is a
// copy property; what you're iterating is not reducedDictionary
// but a snapshot of its keys when the array first began
for (id key in [reducedDictionary allKeys]) {
id child = reducedDictionary[key];
if ([child respondsToSelector:#selector(ty_collectionWithoutNullValues)]) {
reducedDictionary[key] = [child ty_collectionWithoutNullValues];
}
}
return [reducedDictionary copy];
}
#end
#implementation NSArray(RemoveNullValues)
- (NSArray *)ty_collectionWithoutNullValues {
NSMutableArray *reducedArray = [NSMutableArray array];
for (id child in self) {
if ([child isKindOfClass:[NSNull class]]) continue;
if ([child respondsToSelector:#selector(ty_collectionWithoutNullValues)]) {
[reducedArray addObject:[child ty_collectionWithoutNullValues]];
} else {
[reducedArray addObject:child];
}
}
return [reducedArray copy];
}
#end
You must read this answer in conjunction with the accepted answer and comments to the question Is there NSMutableDictionary literal syntax to remove an element?
Following on from the linked answer you can quietly remove all the NSNull's and return nil instead if you access the element using the literal syntax (i.e. not using objectForKey:) by adding the following to your application:
#implementation NSDictionary (ClobberNSNull)
- (id) objectForKeyedSubscript:(id<NSCopying>)key
{
id result = [self objectForKey:key];
return result == NSNull.null ? nil : result;
}
#end
Now when you use the syntax:
dictionary[key]
if the matching object is NSNull then nil will be returned just as if the key did not exist.
There are caveats, see the linked question, and you need to decide if this approach is suitable for your situation. But it is simple.
HTH
Note: Before someone comments, NSNull is a singleton so the == is OK.
use
if(![returnedDict[#"data"][#"weather"][day][#"tides"] isKindOfClass:[NSNull class]]) { some code }
Related
I'm working with a remote library that is delivering this object to me.
If I set my debugger, I get this information :
KandyChatMessage: UUID - 70886A79-2FF60F5E1A3961EF , timestamp - 2017-02-13 17:46:12 +0000 , sender - uri - 3#domain.domain.com, userName - 3, domain - domain.domain.com, type - 0, associationType - 1 , recipient - uri - afdab3bfb5774#domain.domain.com, userName - afdab3bfb57a12b5, domain - domain.domain.com, type - 1, associationType - 1 , type: - 1 , mediaItem - KandyTextMessageData - text:3 , info:(null) , isIncoming - 1 , additionalData - (null), fromHistory - NO
It is delivered via this method :
-(void)_addEventAndRefresh:(id<KandyEventProtocol>)event{
The goal is to convert this object into JSON with something like this :
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:event
options:0
error:&error];
However, this crashes my app as I assume that event doesn't fulfill all the rules of a serializable NSMUtableArray or a NSDictionary for some reason.
This leaves me with two options. One, I can find some crafty method to convert whatever object this is into JSON. Or two, I can cherry-pick its data out and write an NSDictionary Object from scratch.
Would anyone have the slightest clue on how to pick this kind of object apart?
In my debugger, it doesn't seem to respond to anything..
> po event.UUID
=> error: property 'UUID' not found on object of type 'id'
> po event.timestamp
=> error: property 'timestamp' not found on object of type 'id'
You're right that the object is probably not serializable as JSON because it doesn't qualify in one or all of these ways. In a nutshell, it must be a collection of strings, numbers and other collections, and nothing else.
This answer suggests a way to get properties if you know the class. In a nutshell, it relies on objc_property_t *properties = class_copyPropertyList(klass, &outCount); to get the properties.
You can use that to get the object's JSON serializable properties, placing their values (keyed by their name) in a dictionary. Then JSON serialize that.
EDIT
To demonstrate, I added this method to the PropertyUtil class suggested by that other answer...
+ (id)jsonSerializableFromObject:(id)object {
if ([object isKindOfClass:[NSString self]] ||
[object isKindOfClass:[NSNumber self]] ||
[object isKindOfClass:[NSNull self]]) {
return object;
} else if ([object isKindOfClass:[NSArray self]]) {
NSMutableArray *result = [NSMutableArray array];
for (id e in (NSArray *)object) {
id element = [self jsonSerializableFromObject:e];
[result addObject:element];
}
return result;
} else if ([object isKindOfClass:[NSArray self]]) {
NSMutableDictionary *result = [NSMutableDictionary dictionary];
for (id key in [(NSDictionary *)object allKeys])
result[key] = [self jsonSerializableFromObject:(NSDictionary *)object[key]];
return result;
} else {
NSMutableDictionary *result = [NSMutableDictionary dictionary];
NSDictionary *props = [PropertyUtil classPropsFor:[object class]];
for (NSString *propName in [props allKeys]) {
id value = [object performSelector:NSSelectorFromString(propName)];
result[propName] = [self jsonSerializableFromObject:value];
}
return (result.count)? result : [NSNull null];
}
}
Just a quick sketch here: if the operand can be serialized to JSON, return it. If the operand is an array, answer an array of each element made json-serializable. Same if it's it a dictionary. Otherwise, if the object is something arbitrary, apply the introspection method to get it's properties, invoke the getter for each property and answer the json-serializable of that.
So I managed to get a list of all methods like so :
int i=0; unsigned int mc = 0;
Method * mlist = class_copyMethodList(event, &mc);
NSLog(#"%d methods", mc); for(i=0;i<mc;i++)
NSLog(#"Method no #%d: %s", i, sel_getName(method_getName(mlist[i])));
And then I realized I had to class cast the object to retrieve its items :
KandyChatMessage *chatMessage = (KandyChatMessage*)event;
chatMessage.description;
A JSON API feed used by our iOS ObjectiveC app is a bit flaky, so sometimes a field is null.
When parsing JSON we use
NSDictionary *json = [self JSONFromResponseObject:responseObject];
Then try to use the fields with e.g.
[widgetIDArray addObject:widget[#"name"][#"id"]];
Where sometimes the "name" field will be a null. Do we:
1) Ask the API provider to clean up their flaky API code
2) Check for null each and every time we try to use something from the json dict
if ( ![widget[#"name"] isKindOfClass:[NSNull class]] )
3) Use try - catch
#try {
[widgetIDArray addObject:widget[#"name"][#"id"]];
}
#catch (NSException *exception)
{
NSLog(#"Exception %#",exception);
}
ANSWER:
Thanks for the answers, below. Here is the extension to NSObject I added that allows me to get deeply nested JSON items that may or may not be present.
First call with something like
self.item_logo = [self valueFromJSONWithKeyArray:event withKeyArray:#[#"categories",#"bikes",#"wheels",#"model",#"badge_uri"]];
Here is the code in NSObject+extensions.m
- (id) valueFromJSONWithKeyArray:(id)json withKeyArray:(NSArray *)keyArray
{
for (NSString * keyString in keyArray)
{
if ([json[keyString] isKindOfClass:[NSObject class]])
{
json = json[keyString]; // go down a level
}
else
{
return nil; // we didn't find this key
}
}
return json; // We successfully found all the keys, return the object
}
null in a JSON response isn't "flaky", it is absolutely standard.
Even if it was "flaky", any message that you receive from the outside is an attack vector that could allow an attacker to hack into your program, so resilience is required. Crashing when your receive a null allows a DOS attack against your application.
#try / #catch is awful. Exceptions are thrown in response to programming errors. You don't catch them, you fix your code.
How do you fix your code? Simple. Write a few helper methods in an NSDictionary extension.
First you don't know that json is a dictionary. So you add an NSDictionary class method where you pass in anything and it returns what you passed if it is a dictionary and nil (with appropriate logging) if it is anything else.
Next you assume that there is a dictionary under the key "name". So you write an extension "jsonDictionaryForKey" which returns a dictionary if there is one, and nil (with appropriate logging) if it is anything else.
And so on. Make your JSON parsing bullet proof if you want to call yourself a professional developer. For extra bonus points you add a method which will take a dictionary and list all keys that are present that you didn't ask for - so you know if your API is sending things that you don't expect.
You can delete all NSNULL values in your JSON object. Here is a function I used in my library to git rid of all null values in a JSON object.
id BWJSONObjectByRemovingKeysWithNullValues(id json, NSJSONReadingOptions options) {
if ([json isKindOfClass:[NSArray class]]) {
NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:[(NSArray *)json count]];
for (id value in (NSArray *)json) {
[mutableArray addObject:BWJSONObjectByRemovingKeysWithNullValues(value, options)];
}
return (options & NSJSONReadingMutableContainers) ? mutableArray : [NSArray arrayWithArray:mutableArray];
} else if ([json isKindOfClass:[NSDictionary class]]) {
NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:json];
for (id<NSCopying> key in [(NSDictionary *)json allKeys]) {
id value = [(NSDictionary *)json objectForKey:key];
if (isNullValue(value)) {
[mutableDictionary removeObjectForKey:key];
} else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) {
[mutableDictionary setObject:BWJSONObjectByRemovingKeysWithNullValues(value, options) forKey:key];
}
}
return (options & NSJSONReadingMutableContainers) ? mutableDictionary : [NSDictionary dictionaryWithDictionary:mutableDictionary];
}
return json;
}
After all null values have been cleared, perhaps the exceptions will gone too.
An approach that's often used is categories similar to these on NSDictionary and NSMutableDictionary, that ignore nil and NSNull.
#interface NSDictionary (nullnilsafe)
- (ObjectType)nullnilsafe_objectForKey:(KeyType)aKey;
#end
#implementation NSDictionary
- (ObjectType)nullnilsafe_objectForKey:(KeyType)aKey
{
id obj = [self objectForKey:aKey];
if (obj && ![obj isKindOfClass:[NSNull class]] ){
return obj;
}
return nil;
}
#end
#interface NSMutalbeDictionary (nullnilsafe)
- (void)nullnilsafe_setObject:(ObjectType)anObject forKey:(id<NSCopying>)aKey;
#end
#implementation NSMutalbeDictionary
- (void)nullnilsafe_setObject:(ObjectType)anObject forKey:(id<NSCopying>)aKey
{
if (anObject && aKey && ![anObject isKindOfClass:[NSNull class]]){
[self setObject:anObject forKey:aKey];
}
}
#end
The return value of this method is returning null even though the dictionary being passed to is returning the correct data. In debug, I can see that dict is made up of 16 key/value pairs, but when it returns self, my object is returned with no data. I've looked at several examples and my code seems to be the same, but my method is not returning anything.
Any help is greatly appreciated
#import "STHsCardData.h"
#implementation STHsCardData
#synthesize name = _name;
-(instancetype)initWithDictionary: (NSDictionary*) dict{
if (self = [super init]) {
_name = [dict objectForKey:#"name"];
}
return self;
}
In this class, I initialize my STHsCardData object with the dictionary but the name property "newCard" object is always nil instead of the name of the card.
//Loop through data to get to each card object
for (NSDictionary *collection in data)
{
if (collection.count == 0)
{
NSLog(#"array is empty");
}
else
[setArray addObject:collection];
}
for (NSDictionary *set in setArray)
{
for (NSDictionary *cardObj in set){
STHsCardData *newCard = [[STHsCardData alloc]initWithDictionary:cardObj];
[cardArray addObject: newCard];
}
}
return cardArray;
}
#end
Without the data structure of the source of your data it's hard to tell exactly what's wrong. I suspect the issue is either in the nested-for loops you have for setArray and set - should there only be one loop there? Or the key is a different case such as "Name", "NAME", or that key doesn't exist.
It's all but impossible to debug data-driven code like this without feeding it data and walking it through the debugger. Why not add a log statement to see what's going on:
-(instancetype)initWithDictionary: (NSDictionary*) dict
{
if (self = [super init])
{
id nameFromDict = dict[#"name"];
NSLog(#"In initWithDictionary, dict[#"name"] = %#", nameFromDict];
_name = nameFromDict;
}
return self;
}
Trying to check for validity of the data in item
(item is NSDictionary)
I thought this should work but I do get into the second if and crash with:
unrecognized selector sent to instance
becuase galleryArr is (null)
NSArray *galleryArr = [item objectForKey:#"photos"];
if (galleryArr != nil ) {
if ([galleryArr count] != 0) {
//do something
}
}
Any ideas?
I've solved this issue with a simple this simple Objective-C category:
NSDictionary+NotNull.h
#import <Foundation/Foundation.h>
/*! This category extends NSDictionary to work around an issue with NSNull object.
*/
#interface NSDictionary (NotNull)
/*! #abstract Returns the value associated with a given key, but only if the value is not NSNull.
#param aKey The key for which to return the corresponding value.
#return The value associated with the given key, or nil if no value is associated with the key, or the value is NSNull.
*/
- (id)objectOrNilForKey:(id)aKey;
#end
NSDictionary+NotNull.m
#import "NSDictionary+NotNull.h"
#implementation NSDictionary (NotNull)
- (id)objectOrNilForKey:(id)aKey
{
id object = [self objectForKey:aKey];
if (object == [NSNull null]) {
return nil;
}
return object;
}
#end
Now you can just call:
NSArray *galleryArr = [item objectOrNilForKey:#"photos"];
Add a check for [gallryArr isKindOfClass:[NSArray class]].
Maybe you get NSNull? That's a singleton ([NSNull null]) object that represent nil. You can check if([gallryArr isKindOfClass:[NSArray class]]).
I'm writing an iOS App where i need to get data from a SQL-Database over mobile Services from Azure.
After downloading the data I get a NSDictionary with all attributes from the SQL-Table. If an attribute is empty, the value is NSNull.
Is there a way to pass NSNull to NSString without an IF-Statement (I don't want to have 20 if statements..)?
I wrote a category just for dealing with this issue. I used it with Core Data but it should help you, too.
#interface NSDictionary (Extensions)
- (id)NSNullToNilForKey:(NSString *)key;
#end
#implementation NSDictionary (Extensions)
- (id)NSNullToNilForKey:(NSString *)key
{
id value = [self valueForKey:key];
return value != [NSNull null] ? value : nil;
}
#end
Sample use:
NSString *value = [dictionary NSNullToNilForKey:#"key"];
You can't just assign it, but you can filter out all of the NSNull instances using something like this:
NSDictionary *dictionary = // data from server
NSDictionary *filteredDictionary = [dictionary mutableCopy];
NSSet *keysToRemove = [orig keysOfEntriesPassingTest:^BOOL(id key, id obj, BOOL *stop) {
if (obj == [NSNull null]) {
return YES;
} else {
return NO;
}
}];
[filteredDictionary removeObjectsForKeys:[keysToRemove allObjects]];
Now you have the same dictionary except that every key with an NSNull has been removed.