I am using JSON-RPC to communicate between iOS application and the server. Some return values of the server are optional.
Considering the combination of technologies I am using, is it better to return these null values like {"telephone": null} or by completely omitting the "telephone" element in the response?
Futher explanation of what I am asking:
It doesn't look like the JSON-RPC spec specifies much to do with the method result (please correct me if I'm wrong) and clearly not sending loads null elements would improve performance and reduce bandwidth somewhat. What I'm most interested in though is the best approach from an iOS NSJSONSerialization perspective. Is it easier/better to check for whether a key exists in an NSDictionary or whether an existing key has a null value?
I'm using a completely different technique, somewhat unorthodox for sure, since my code is dealing with huge data sets and I really hate having to make tests for NSNull on each value. What I did was subclass NSNull, so when dealing with data, I can test if its numeric value is 0, if its string length is 0, etc.
#implementation NSNull (JSON)
- (NSUInteger)length { return 0; }
- (NSInteger)integerValue { return 0; };
- (CGFloat)floatValue { return 0; };
- (NSString *)description { return #"0(null)"; } // so I know it was NSNull in log messages
- (NSArray *)componentsSeparatedByString:(NSString *)separator { return #[]; }
- (id)objectForKey:(id)key { return nil; }
- (BOOL)boolValue { return NO; }
#end
EDIT: I used this exact same code in an e-commerce shipping app. There were literally thousands of objects getting returned by tens of different APIs - having to look at each item to see if it was [NSNull null] would have been a nightmare. I tried to write a routine to groom results, it would recursively look at dictionaries and arrays and reconstruct the object, but it got too complicated.
In any case, I never even once had an issue with this solution. Obviously YMMV.
First of all {"telephone": "null"} is not a null value. There are quotes so actually telephone property has string value with text "null". Server needs to send {"telephone": null} - without quotes.
I'd go with listing all relevant properties that your app needs in response and put null as a value if there is not value. Then you can easily check in NSDictionary you get from NSJSONSerialization if value for key is NSNull.
if ([dictionaryValue isKindOfClass:[NSNull class]])
myThink.myProperty = nil;
I've packed the check in a function, if anyone interested.
-(id)checkNull:(id)object{
if([object isKindOfClass:[NSNull class]])return nil;
else return object;
}
Related
I'm new to objective c and have been given an existing, outsourced codebase so I apologise if I am missing something very obvious!
I have a Goal data model which I have added a date to:
When making a request to the db for an array of goals, the following is returned:
As you can see, one of the array elements returns an extra KVP which is the date type I added to the xcdatamodel previously. This date refers to the date in which the goal was completed.
When iterating over this array in a for loop and checking whether the date KVP is not nil, NULL is returned for the object in which the date exists for:
([[self.goalArray objectAtIndex:i] valueForKey:#"date"] != nil) ?
[[self.goalArray objectAtIndex:i] valueForKey:#"date"] : [NSNull null]
However, the other values in the object using the same above expression with their keys return their expected values:
It's worth noting that in order to get the app to run, I change the date xcdatamodel to a string value as opposed to date for testing purposes.
I have also added this to the Goal model:
#synthesize date;
...
-(NSDate*) date {
return date;
}
-(void) setDate:(NSDate*)DateTime {
date = DateTime;
}
Is there something obvious I'm missing as I am at a wits end?
Again I apologise if this is a bad question I cannot find any clues as to how to handle objects which may or may not return a certain KVP
You should probably use objectForKey:, that's a NSDictionary function. The valueForKey: basically works on any class and returns a value and/or function result based on the key you pass it.
I can imagine something funky happening when asking for the date.
Your code should probably look somewhat like the following:
NSDictionary *goalInfo = self.goalArray[i];
//goalInfo[#"date"] - This is the short, and more readable version of 'objectForKey:'
if (goalInfo && goalInfo[#"date"]) {
return goalInfo[#"date"];
}
else {
return [NSNull null];
}
I want to check if a JSON object is an NSString and if it isn't, assign it a default string. My ultimate goal is to prevent crashing and assign the properties a proper value no matter what. This is an example of a data model I am using where dict is the JSON dictionary the API returns.
Data *data = [[self alloc] init];
data.name = [NSString validateString:dict[#"name"] defaultString:#""];
data.status = [NSString validateString:dict[#"status"] defaultString:#"OPEN"];
Here is the category method validateString I am using.
+ (NSString *)validateString:(NSString *)aString defaultString:(NSString *)defaultString {
if ([aString isKindOfClass:[NSString class]]) {
return aString;
}
return defaultString;
}
It makes no sense, and is very bad practice, to cast (NSString *)aString and then ask if this is in fact an NSString.
Also, what if it is nil?
All you know when you fetch from a dictionary is that you get an id. Do not assume more than that.
I would suggest writing very plainly: say what you mean, and mean what you say. That is the best practice in Objective-C. Otherwise, dynamic typing and "nil trickery" can lead you into subtle errors. You might not have any trouble in this particular case, but bad habits are bad habits, and it is best not to let them form in the first place. I'd rewrite like this:
+ (NSString *) checkType:(nullable id)obj defaultString:(NSString *)def {
if (obj == nil || ![obj isKindOfClass:[NSString class]]) {
return def;
}
return obj;
}
Like mentioned in other comments: if you want to prevent crashes, you also need to check if it's nil, specially if there is a chance to port your code to Swift in the future.
Just to clarify my last sentence, the line below works in Objective-C even if aString is nil:
if ([aString isKindOfClass:[NSString class]]) {
That's because, in the way Objective-C was made, calling a function on a nil object returns nil, so the if will be considered false, and the function will return defaultString. Yeah... that's certainly a bad idea when they created Objetive-C, since this leads to lots of errors. More details about that behaviour below:
https://stackoverflow.com/a/2696909
Anyway, it's also a good practice to only cast an object after checking its type, so I would recommend adapting your function to this:
+ (NSString *)validateString:(id)obj defaultString:(NSString *)defaultString {
if (obj != nil && [obj isKindOfClass:[NSString class]]) {
return (NSString*)obj;
}
return defaultString;
}
Every object that implements NSObject* has isKindOfClass: (and NSDictionary* only stores objects that implement NSObject*), so we don't need to check if the object responds to it. Also, even if we wanted, respondsToSelector: is also an NSObject* function.
Still, the method that you are using still works. The revised function above is just adapted to better practices and to avoid problems in case you ever need to port this code to Swift (or any other language) in the future.
EDIT: Updated code based in #matt's suggestion.
I have hired a iOS developer to create an app which will be backed by the REST API. Now I'm stuck with a problem with one output.
There are Public and Private Groups, if group is Private, the API will return following in json format:
privacy":{"value":"1"},
and if the group is Public, the API will return following in json format:
"privacy":[]
The iOS developer says that this output is incorrect while on the other hand API developer believe this is correct output. Can anyone please tell me is this output correct to be used in iOS app or it's not correct?
iOS Developer days he can't compare String and Array.
Yes it's correct, given there is no such thing as incorrect with JSON, as there is no schema to conform to. As long as it's legal, it's OK.
The iOS developer can test the type of the "privacy" value after it's been deserialised:
id value = jsonDict[#"privacy"];
if ([value isKindOfClass:[NSDictionary class]]) {
// Value is dictionary
NSDictionary *dictValue = (NSDictionary *)value;
NSString *number = dictValue[#"value"]; // This should be a number, not a string!
} else if ([value isKindOfClass:[NSArray class]]) {
// Value is array
} else {
// Value is illegal. Report error.
}
I will say that it should be:
{"value":1}
as 1 is a number, not a string.
Yes, iOS developer can check response.
But there should be consistency in JSON response.
It is not correct way that one API give response in array and other in dictionary.
It should be either array or string that would be preferable for iOS developer.
Output should be:
{
"privacy":[{"value":1}]
}
For validating JSON response you can use http://jsonlint.com/
The API is designed incorrectly, as it provides various data types for the privacy key (and no schema defines how this should behave). Once it's a dictionary, once it's an empty array.
I'd suggest using an array in any case.
Private:
privacy : [ {"value" : true} ]
Public:
privacy : []
However, it's possible to concatenate array to string and then compare with string (using let stringRepresentation = ",".join(array))
This may sound like a silly question but Apple provides us with NSNotFound but why didn't they provide one called NSFound? Is there a way one can define a NSFound macro on their own?
The reason I am asking all this is that in order for me to check if a string "contains" a certain character I have to do double negative i.e.
if ([XML rangeOfString:#"error" options:NSCaseInsensitiveSearch].location != NSNotFound)
{
//server is down
}
else
{
//server is up
}
At least for me this would have been so much easier to read if I could simply do this instead
if ([XML rangeOfString:#"error" options:NSCaseInsensitiveSearch].location == NSFound)
{
//server is down
}
else
{
//server is up
}
If I want to define NSFound or SAMFound, how would I go about doing that?
Your issue is really with the design pattern methods like rangeOfString follow - using a single return value for both valid results, of which there are many, and failure indications, of which there is one. You can test for a single failure value with a comparison to a constant, NSNotFound in this case, but you cannot likewise test for many possible values with a simple comparison - instead you use the "double negative" you don't like.
If you find it too ugly change it... Maybe:
#interface NSString (SamExtras)
- (BOOL) SAMcontainsString:(NSString *)string options:(NSStringCompareOptions)options;
#end
#implementation NSString (SamExtras)
- (BOOL) SAMcontainsString:(NSString *)string options:(NSStringCompareOptions)options
{
return [self rangeOfString:string options:options].location != NSNotFound;
}
#end
Which would allow you to use:
if ([XML SAMcontainsString:#"error" options:NSCaseInsensitiveSearch])
{
//server is down
}
else
{
//server is up
}
with no double negative. You can write the category once and use it in all your projects.
HTH
Double Negative doesn't have the consequences in code as it does in grammar.
The reason they provide a not found, as opposed to a found version, is simply the not found value is a single (supposedly invalid) value and everything else is valid. It's therefore simpler to define this single, invalid value.
Also it makes more sense (more efficient, avoiding a double-search and less code) to store the NSRange in a local variable in order to firstly test for validity and then to use the value:
NSRange range = [XML rangeOfString:#"error" options:NSCaseInsensitiveSearch];
if (range.location != NSNotFound) {
// Do thing with range
} else {
// Complain
}
There is nothing whatever wrong with your original test:
if ([XML rangeOfString:#"error" options:NSCaseInsensitiveSearch].location != NSNotFound) {
If all you need to know is whether XML contains the string #"error", that test answers the question and is a perfectly legitimate and idiomatic way to ask it. Observe that even the documentation tells you that containsString: is nothing but a front for calling rangeOfString:options:!
If you really want to know what the positive version would be, it would be to test the length of the returned range and see if it is the same as the length of #"error". The length of a not-found range is 0.
In Objective C, a common practice is to often forgo checking for nil, instead relying on the fact that messaging nil silently fails.
For example, to validate a string:
if ([myString length] > 1) { // Short and sweet
If myString is nil, this check will fail - as intended. It's a huge improvement over what one may assume would be the correct implementation, explicitly checking for nil:
if (myString && [myString length] > 1) { // Redundant nil check
However, particularly in the case of a web API, myString could conceivable equal [NSNull null] - similar to nil, but with different behavior. Of primary concern, is that messaging NSNull causes an exception. For example, our short simple solution from earlier will cause an exception:
// Causes an exception, because [NSNull null] is an object
NSString *myString = (id)[NSNull null];
if ([myString length] > 1) {
To further complicate things, a simple nil check will actually pass because [NSNull null] is a valid object:
NSString *myString = (id)[NSNull null];
if (myString) { // Evals to YES
So, the only way to be completely safe is to both check for NSNull and do your regular implicit nil check, by calling a method:
if (myString != (id)[NSNull null] && [myString length] > 1) { // Short and sweet
My Question: is this really necessary? Is there a more concise option with less duplicate code, that I've overlooked? Are my conclusions, in fact, correct?
NSNull objects typically crop up with people reading JSON files containing null values.
In that case, there is a chance that the server that supplied the null value thinks you should do something different than with no value. For example, if you get a dictionary and there might be a string stored under some key, you might get a string with non-zero length, you might get an empty string because the server sent you an empty string, you might get nothing because the server sent you nothing, or you might get [NSNull null] because the server sent you a null value. Up to you to decide if the empty string, nothing, and [NSNull null] need to be treated differently or not.
By the way: I have always used some extensions to the NSDictionary class, for example - (NSString*)stringForKey, which will return either an NSString or nil, 100% guaranteed. If you do that, all your error checking is in one method. And you can do things like converting NSNumber to NSString, or NSNull to nil, or NSDictionary etc. to nil, and never worry about it again. Of course you need to check if it is appropriate.
is this really necessary?
If some API can return NSNull instead of a string, then yes, you should check for it. It's usually not necessary since NSNull is not a string and won't be returned by most methods that would return a string. But a collection such as a dictionary, for example, can have any object as a value. If you're not sure that the value will be a string, you should take appropriate precautions.
Is there a better way to handle the possibility of NSNull that I've overlooked?
Another way that you could do roughly the same thing but protect yourself from other types (NSNumber, for example) is to check the type of the thing that you get back rather than comparing specifically against [NSNull null]:
if ([myString isKindOfClass:[NSString class]] && [myString length] > 1) { //...
In addition to the other answers, you could consider replacing NSNull instances with empty strings before parsing your API response into model objects. Similarly, you could traverse a mutable copy of the response dictionary and remove keys whose values contain NSNull.
Basically, do it up front when you get the response so that your code isn't littered with NSNull checks.
Another way to approach this is to use a Category:
#interface NSNull (Utilities)
- (NSString *)stringValue;
#end
#implementation NSNull (Utilities)
- (NSString *)stringValue
{
return nil;
}
#end
And then simply:
[myDictionary[#"key"] stringValue] // returns nil for NSNull values