Resilience in JSON parsing with ObjectiveC - ios

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

Related

Is there a way to acquire the properties from this obscure object and convert it to JSON?

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;

objectForKey throws an exception on count if it contains a single value

I have a dictionary JSON dictionary like
{
county = "XXXXX";
states = (
"YYYYY",
"ZZZZZ"
);
}
After reading the JSON dictionary the data is in an array. I then want to create a different array with the data and I am using the following code
for (NSString *key in array) {
NSMutableArray *myData =[[NSMutableArray aalloc] init];
myData = [array objectForKey:key];
int count = [myData count];
}
When key is 'states', it works fine. If key is 'country', then it throws an exception on count because objectForKey returns a NSString. How to resolve this? How to find how many objects the objectForKey will return?
Check out this answer from #HDdeveloper:
if([object isKindOfClass:[NSString class]] == YES)
{
NSLog(#"String rx from server");
}
else if ([object isKindOfClass:[NSDictionary class]] == YES)
{
NSLog(#"Dictionary rx from server");
}
else if ([object isKindOfClass:[NSArray class]] == YES)
{
NSLog(#"Array rx from server");
}
Basically, after you get the objectForKey, then you need to check what kind of object that is and use it accordingly.
Your JSON contains a dictionary.
The key "states" in that dict contains an array, and the other key contains a string. Neither key contains an NSData object.
Arrays have a count method, but strings do not. That's why you're crashing.

Check if key of NSMutableDictionary is nil

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 }

Assign NSNull Object to NSString

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.

sort NSArray with objects

Supposing NSArray has several objects which belong to two classes,
#interface FWNewsObj:NSObject
{
NSString *newsTitle;
NSDate *newsTime;
}
#end
#interface FWPhotoObj:NSObject
{
NSString *photoTitle;
NSDate *photoTime;
}
#end
I'd like to sort the NSArray with object's title (or time). However the title variable in each class has different names.
Then How can I do the sort?
Thanks.
What is instantly coming to my mind is(If I am understanding the Q correctly) :
1st create a dictionary, with titleNames as the keys, and value as the objects
Sort the keys array simply
Then create a sorted array of objects pulling them out of the dictionary, on basis of this sorted keys array
You have to write a custom compare method that both your classes implement. It must take one object as a parameter and return an NSComparisonResult (NSOrderedAscending, NSOrderedDescending or NSOrderedSame)
You can then use sortedArrayUsingSelector: with your own comparison method.
Example:
In FWNewsObj:
- (NSComparisonResult)compareTitle:(id)obj
{
NSAssert([obj isKindOfClass:[FWNewsObj class]] || [obj isKindOfClass:[FWPhotoObj class]], #"Don't know how to compare %# to %#", self, obj);
if ([obj isKindOfClass:[FWPhotoObj class]]) {
return [newsTitle compare:[(FWPhotoObj *)obj photoTitle]];
} else {
return [newsTitle compare:[(FWNewsObj *)obj newsTitle]];
}
}
In FWPhotoObj:
- (NSComparisonResult)compareTitle:(id)obj
{
NSAssert([obj isKindOfClass:[FWNewsObj class]] || [obj isKindOfClass:[FWPhotoObj class]], #"Don't know how to compare %# to %#", self, obj);
if ([obj isKindOfClass:[FWPhotoObj class]]) {
return [photoTitle compare:[(FWPhotoObj *)obj photoTitle]];
} else {
return [photoTitle compare:[(FWNewsObj *)obj newsTitleTitle]];
}
}
It would actually be easier to just define a title method in both classes that wraps either the photoTitle or the newsTitle. Then you can just use NSSortDescriptor with title as the key.

Resources