I think I get it how to use the NSJSONSerialization over all. The call I am making is:
[NSJSONSerialization dataWithJSONObject:parameters options:0 error:&error]
where parameters is a NSDictionary which includes keys and values to use in the JSON request. Everything is fine and all, until I have to use null as a value instead of an empty string. I can't find a way to set the value as null at all. The example of json that I need to create is:
{"target"
{"firstname":<firstname>,
"lastname":<lastname>},
"error":null
}
The way server is setup is that it's expecting either an error as a string or a null if there's no error.
Any ideas? Am I missing an API call or something like that?
Thanks!
You have to use NSNull. For instance
Swift
let dict = ["firstKeyHasValue": 2342, "secondKeyHasNoValue": NSNull()]
Objective-C
NSDictionary *dict = #{ #"error": [NSNull null] };
From the official documentation of NSDictionary:
Neither a key nor a value can be nil; if you need to represent a null value in a dictionary, you should use NSNull.
I tried doing this and worked for me.
var param: [String:Any] = [:]
param["firstName"] = "somename"
param["middleName"] = NSNull()
param["lastName"] = "somename"
print(param)
Result:
["firstName":"somename","middleName":null,"lastName":"somename"]
If you are using Swifty JSON, I've just created a PR that should handle nil. It's not merged yet and might need some improvements.
The part that describes how to do that should be a good start to write your own implementation if you need to. The logic is not that complicated but there might be tricky edge cases/performance things to think about.
I know it's not a direct response to the question since it's replacing NSJSONSerialization, but I hope it can help some people frustrated by how Swift handles nil/json!
Just try something like this:
NSDictionary *jsonObj = [NSDictionary dictionaryWithObjectsAndKeys:#"My Name", #"name", #"Developer", #"occupation", NULL, #"My NULL field", nil];
and use this object as parameter where you want it.
Related
The basic gist: I've got some JSON coming back from a webservice to validate a login; that part works. I'm pulling values out of the array into an NSDictionary; that part works. I need to check one of the values that comes back to know if it was successful or not. That's where it's failing. And as far as I can tell, it's telling me that "success" is not equal to "success".
NSDictionary *jsonArray = [NSJSONSerialization JSONObjectWithData: response options: NSJSONReadingMutableContainers error: &err];
NSString *result = [jsonArray valueForKey:#"result"];
NSLog(#"%#",result);
if ([result isEqual:#"success"]) {
The log shows "result" is getting set as "success", but it never seems to evaluate as true.
If I set "result" manually:
NSString *result = #"success";
...it gets into the if statement just fine, so it seems like there's something I'm missing that's pointing to a data type or something similar... I'm just at a loss of what else to try at this point.
I'm normally a web dev, but I'm new to iOS, so my debugging is still a little lacking in xcode but I'm familiar with general logic and such. Any help you guys could give me would be fantastic!
Edit:
NSLog showing the JSON coming back from the webservice:
2014-01-10 16:22:42.568 LoginTest[1640:70b] (
{
code = 1;
fname = Joe;
lname = Tests;
result = success;
token = 2555f13bce42b14cdc9e60b923bb2b20;
vendornum = 50000000;
}
)
Edit - final working code:
NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData: response options: NSJSONReadingMutableContainers error: &err];
NSLog(#"jsonArray: %#", jsonArray);
NSString *result = [jsonArray[0] objectForKey:#"result"];
NSLog(#"%#",result);
if ([result isEqual:#"success"]) {
Earlier you commented (from a now removed answer) that -isEqualToString: threw an unrecognized selector. I believe it was -[__NSCFArray -isEqualToString:] or something very similar.
Based on your comment, you don't have "success", you have [ "success" ] in your JSON.
That is an array which wraps the value of a string. You need to get the first element of the array and use that.
[result[0] isEqual:#"success"]
Based on the output in your log, your JSON is not an object
{
…
"result" = "success"
…
}
It is an array with only one object in it.
[
{
…
"result" = "success"
…
}
]
You are working with an array of data so the output of -valueForKey: will be an array of data.
#MartinR is correct, it may be clearer to use
[jsonArray[0] objectForKey:#"result"]
to get the result.
You didn't show us the actual output of the log. That's bad. Deducing from your comments, it should have shown something like
(
"success"
)
which is the description of an array object (NSArray) containing a string, rather than the string object itself.
If this is indeed the case, then you need to get the (only? first?) element in the array and compare that using isEqual: or isEqualToString:.
if we want to compare two NSString we use [str1 isEqualToString:str2]; You should do same instead of isEqual:
isEqual: compares a string to an object, and will return NO if the object is not a string. do it if you are not sure if object is NSString.
`isEqualToString:` use it when you are sure both Objects are NSString.
I am working with objective c for an iphone app.
I see that [dictionary objectForKey:#"key"] return <null>. Doing a if([dictionary objectForKey:#"key"] == nil || [dictionary objectForKey:#"key"] == null) does not seem to catch this case.
Doing a if([[dictionary objectForKey:#"key"] isEqualToString:#"<null>"]) causes my program to crash.
What is the correct expression to catch <null>?
More Details
An if statement for nil still isn't catching the case... Maybe i'm just too tired to see something, but here's additional info:
Dictionary is populated via a url that contains json data like so:
NSURL *url = [NSURL URLWithString:"http://site.com/"];
dataresult = [NSData dataWithContentsOfURL:url];
NSError *error;
NSMutableDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:dataresult options:kNilOptions error:& error];
doing an NSLog on the dictionary gives this output:
{
key = "<null>";
responseMessage = "The email / registration code combination is incorrect";
}
You have an instance of NSNull. Actually, the instance, since it's a singleton.
Cocoa collections can't contain nil, although they may return nil if you try to access something which isn't present.
However, sometimes it's valuable for a program to store a thing meaning "nothing" in a collection. That's what NSNull is for.
As it happens, JSON can represent null objects. So, when converting JSON to a Cocoa collection, those null objects get translated into the NSNull object.
When Cocoa formats a string with a "%#" specifier, a nil value will get formatted as "(null)" with parentheses. An NSNull value will get formatted as "<null>" with angle brackets.
New answer:
Thanks for adding the detail. It looks like the "dataresult" you are setting is not a JSON object so no wonder you're getting wacky results from putting a raw string into "[NSJSONSerialization JSONObjectWithData:]. You may need to do some basic error checking on your data before you call anything JSON related.
Original answer:
First off, if this were my code, I wouldn't name a "NSDictionary" object "array" (and I see you caught my comment in your edit... hope you get some sleep soon!).
Secondly, what you are looking for is "nil", not a string named "<null>". As Apple's documentation for objectForKey: states, if an object is not found for the key you are asking for, a nil is returned. The Xcode console tries to be helpful in converting nil objects to "<null>" strings in it's output.
Do "if [dictionary objectForKey: #"key"] != nil" and you should be happier.
Just use the following code:
if ([[dictionary valueForKey:#"key"] isKindOfClass:[NSNull Class]]{
//This means that the value inside the dictionary is <null>
}
else{
//the value is not <null>
}
This should do it.
I'll use an example from JavaScript to help clarify my question. Let's assume I have the following object:
sports = {
soccer: {...},
basketball: {...},
baseball: {...}
}
If at some point in my script I have a variable, sportString, that simply holds a string, I can dynamically call one of the sports objects in the following way:
sports[sportString];
This frees me from having to use a bunch of nested if statements, testing the value of the string such as:
if(sportString === 'soccer'){
sports.soccer;
}else if(sportString === 'basketball){....
So, my question is how can I accomplish something similar to sports[sportString] in Objective-C, if sportString is an NSString object?
Use an NSDictionary as your sports object. Then you can do lookups like this:
[sports objectForKey: sportsString];
The people saying you should use NSDictionary for general key/value storage are 100 % right. However, I think it’s useful to know that you can call a message specified by a string:
SEL selector = NSSelectorFromString(#"foo"); // Or #selector(foo) if you know it at compile time
id value = [object performSelector:selector];
You can also use selectors with up to two arguments, as long as they take objects:
SEL selector2 = NSSelectorFromString(#"setFoo:");
[object performSelector:selector2 withObject:value];
It’s possible to invoke arbitrary methods using IMPs or casting objc_msgSend(), but now I’m getting way beyond the scope of your actual question. :-)
Your JavaScript object sports would typically be an NSDictionary or NSMutableDictionary.
Example:
NSMutableDictionary *sports = [NSMutableDictionary dictionary];
[sports setObject:#"Foo" forKey:#"soccer"];
[sports setObject:#"Bar" forKey:#"basketball"];
NSString *sportString = #"soccer";
NSString *sportValue = [sports objectForKey:sportString];
NSLog(#"%#", sportValue); //logs "Foo"
Hi
I am using TouchJSON to deserialize some JSON. I have been using it in the past and on those occasions I dealt with occurrences of NSNull manually. I would think the author had to deal with this as well, so me doing that again would just be overhead. I then found this in the documentation:
Avoiding NSNull values in output.
NSData *theJSONData = /* some JSON data */
CJSONDeserializer *theDeserializer = [CJSONDeserializer deserializer];
theDeserializer.nullObject = NULL;
NSError *theError = nil;
id theObject = [theDeserializer deserialize:theJSONData error:&theError];}
The way I understand it the user of the class can pass a C-style null pointer to the deserializer and when it encounters a NSNull it will insert the values (NULL) passed to it. So later on when I use the values I won't get NSNull, but NULL.
This seems strange, the return value is an NSDictionary which can only contain Objects, shouldn't the value default to 'nil' instead?
If it is NULL can I check the values like this?
if([jsonDataDict objectForKey:someKey] == NULL)
It would seem more logically to be able to do this:
if(![jsonDataDict objectForKey:someKey])
No to mention all the cases where passing nil is allowed but passing NULL causes a crash.
Or can I just pass 'nil' to the deserializer?
Much of this stems from me still struggling with nil, NULL, [NSNULL null], maybe I am failing to see the potential caveats in using nil.
For another JSON library, but with the same issues, I've created the following category on NSDictionary:
#implementation NSDictionary (Utility)
// in case of [NSNull null] values a nil is returned ...
- (id)objectForKeyNotNull:(id)key {
id object = [self objectForKey:key];
if (object == [NSNull null])
return nil;
return object;
}
#end
Whenever I deal with JSON data from said library, I retrieve values like this:
NSString *someString = [jsonDictionary objectForKeyNotNull:#"SomeString"];
This way the code in my projects become a lot cleaner and at the same time I don't have to think about dealing with [NSNull null] values and the like.
nil and NULL are actually both equal to zero, so they are, in practice, interchangeable. But you're right, for consistency, the documentation for TouchJSON should have used theDeserializer.nullObject = nil instead of NULL.
Now, when you do that, your second predicate actually works fine:
if (![jsonDataDict objectForKey:someKey])
because TouchJSON omits the key from the dictionary when you have nullObject set to nil (or NULL). When the key doesn't exist in the dictionary, NSDictionary returns nil, which is zero so your if condition works as you expect.
If you don't specify nullObject as nil, you can instead check for null like so:
if ([jsonDataDict objectForKey:someKey] == [NSNull null])
There are libraries which deal with it. One of them is SwiftyJSON in Swift, another one is NSTEasyJSON in Objective-C.
With this library (NSTEasyJSON) it will be easy to deal with such problems. In your case you can just check values you need:
NSTEasyJSON *JSON = [NSTEasyJSON withData:JSONData];
NSString *someValue = JSON[someKey].string;
This value will be NSString or nil and you should not check it for NSNull, NULL yourself.
OK, I'm a little confused.
It's probably just a triviality.
I've got a function which looks something like this:
- (void)getNumbersForNews:(BOOL)news andMails:(BOOL)mails {
NSMutableDictionary *parameters = [[NSMutableDictionary alloc] init];
[parameters setValue:news forKey:#"getNews"];
[parameters setValue:mails forKey:#"getMails"];...}
It doesn't matter whether I use setValue:forKey: or setObject:ForKey:, I'm always getting a warning:
"Passing argument 1 of set... makes pointer from integer without a cast"...
How on earth do I insert a bool into a dictionary?
Values in an NSDictionary must be objects. To solve this problem, wrap the booleans in NSNumber objects:
[parameters setValue:[NSNumber numberWithBool:news] forKey:#"news"];
[parameters setValue:[NSNumber numberWithBool:mails] forKey:#"mails"];
Objective-C containers can store only Objective-C objects so you need to wrap you BOOL in some object. You can create a NSNumber object with [NSNumber numberWithBool] and store the result.
Later you can get your boolean value back using NSNumber's -boolValue.
Modern code for reference:
parameters[#"getNews"] = #(news);
A BOOL is not an object - it's a synonym for an int and has 0 or 1 as its values. As a result, it's not going to be put in an object-containing structure.
You can use NSNumber to create an object wrapper for any of the integer types; there's a constructor [NSNumber numberWithBool:] that you can invoke to get an object, and then use that. Similarly, you can use that to get the object back again: [obj boolValue].
You can insert #"YES" or #"NO" string objects and Cocoa will cast it to bool once you read them back.
Otherwise I'd suggest creating dictionary using factory method like dictionaryWithObjectsAndKeys:.
Seeing #Steve Harrison's answer I do have one comment. For some reason this doesn't work with passing object properties like for e.g.
[parameters setValue:[NSNumber numberWithBool:myObject.hasNews] forKey:#"news"];
This sets the news key to null in the parameter NSDictionary (for some reason can't really understand why)
My only solution was to use #Eimantas's way as follows:
[parameters setValue:[NSNumber numberWithBool:myObject.hasNews ? #"YES" : #"NO"] forKey:#"news"];
This worked flawlessly. Don't ask me why passing the BOOL directly doesn't work but at least I found a solution. Any ideas?