Resolve numbers and bools in nested NSDictionary - ios

Let's say I loaded a JSON string into an NSDictionary that had some numbers written as strings. The resulting NSDictionary might look something like this:
NSDictionary* example = #{
#"aNumber": #"1",
#"aFloat": #"2.9708",
#"aBool": #"true",
#"aNestedDict": #{
#"more": #"220",
#"evenMore": #"false",
#"anArray": #[
#"1",
#"2"
]
}
};
I want to parse the float, integer, and bool ('true', 'false', 'yes', 'no' - case insensitive) values into their respective Objective-c class types. I've looked around, but can't find any examples of built in APIs to do this.
(Enlarged since people aren't reading the question)
Am I stuck writing a recursive parser and converting each value manually, or does Apple offer a built-in API to recursively parse it for me?

There isn't an API to do it, however you can make a helper function to figure it out. The API that apple does provide however are helper functions on NSString, i.e.: .integerValue, .doubleValue, .boolValue. However not only is this limited to NSString, it's also not comprehensive / intelligent.

So if you want to parse the string into a variable of type BOOL you can do something as simple as:
- (NSNumber *)parseBool:(NSString *)value
{
if( [value caseInsensitiveCompare:#"yes"] == NSOrderedSame || [value caseInsensitiveCompare:#"true"] == NSOrderedSame )
{
return #YES;
} else if ([value caseInsensitiveCompare:#"no"] == NSOrderedSame || [value caseInsensitiveCompare:#"false"] == NSOrderedSame )
{
return #NO;
} else
{
return nil;
}
}
EDIT:
For int and double just use:
NSString *string = #"1";
NSInteger intValue = string.integerValue;
double doubleValue = string.doubleValue;

JSON supports strings, numbers with and without decimals, boolean values, null values, dictionaries and arrays. So anyone wanting to represent numbers and boolean values in JSON can just do that.
Anyone producing JSON should document what they are producing. So if they insist on representing a boolean value as a string, then they should document which possible strings will be used to represent true and false. And then it's just a matter of string comparison.
For numbers stored as string, you can use integerValue or doubleValue which works just fine for strings.

Related

Objective C: A cleaner way to check if a string isn't empty before creating and assigning a string to a dictionary key?

I am creating an NSMutableDictionary and assigning an NSString (test and test1) to a parameter key.
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
if (test.length) {
dictionary[#"test"] = test;
}
if (test1.length) {
dictionary[#"test1"] = test1;
}
This method does work. However, I am going to eventually have more strings and don't want a bunch of if statements. I don't want the dictionary keys to exist if the string is empty or nil.
Not sure if there is a way around this.
I thought about creating a separate function that accepts an array of key string and array of string values and use a for loop to see if string value is empty. Then, return a dictionary once the for loop ends. However, you can't insert nil into an NSArray
Something like this
- (void)updateDic:(NSMutableDictionary *)dic withString:(NSString *)str {
if (!str || [str isEqualToString:#""]) {
return;
}
dic[str] = str;
}
And then just iterate over all strings and use that method.
What I'd do is create a NSMutableDictionary category, something like this:
NSMutableDictionary+CustomMethods.m:
- (void)setStringIfNotNil:(NSString *)string forKey:(id <NSCopying>)key {
if (!string || !string.length) { return; }
self[key] = string;
}
Then you can use it like this:
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
[dictionary setStringIfNotNil:test forKey:#"test"]
[dictionary setStringIfNotNil:test1 forKey:#"test1"]
There are three ways according to me..
The first one use the category in which you have to write if condition only single time and you can use it in any class of your project.
Second one by making a separate method to do that task(to check string nil or not and adding into the dictionary).
And the third one , just add all in an array and and perform the action in a loop.
The syntax:
dictionary[#"test"] = test
will remove the value from the dictionary if test is nil (it's a difference between the normal -setObject:forKey: method and the -setObject:forKeyedSubcript: which that syntax invokes). However, that will not work for empty strings.
As mentioned in another answer, you could make an NSDictionary category method to check, then call that method instead.
You could also just use the regular dictionary[key] = value syntax, then when you are done, do:
[dictionary removeObjectsForKeys:[dictionary allKeysForObject:#""]];
If it's possible to have the keys repeated, and you don't want an empty string overriding an earlier valid valid, you would have to check each time -- either by the category method, or using a local macro or inline function or local method.
static inline void SetValidVal(NSMutableDictionary *dictionary, NSString *key, NSString *val) {
if (val.length) { dictionary[key] = val; }
}
or
#define MY_SET_VALID_VAL(dictionary, key, val) if ((val).length) dictionary[key] = (val)
If the key names need to be the same as the name of the local variable, you can play other games with macros (this also assumes the local variable name "dictionary":
#define MY_UPDATE_VAL(val) if (val.length) dictionary[##val] = (val)
Then MY_UPDATE_VAL(test1); would expand to:
if (test1.length) dictionary[#"test1"] = test1;
That's a bit magic though and probably not recommended.

Concatenate Strings for Dictionary:syntax error

The following code to conditionally concatenate strings for a dictionary seems to work up to the point where I try to place the concatenated result in the dictionary. Can anyone see the error?
NSDictionary *jsonDictionary;
NSString* dictString = #"#\"first\":first,#\"last"
NSString *dictString2=dictString;
if (date.length>0&&![date isKindOfClass:[NSNull class]]) {
//only include this key value pair if the value is not missing
dictString2 = [NSString stringWithFormat:#"%#%s", dictString, "#\"date\":date"];
}
jsonDictionary = #{dictString2}; //syntax error. Says expected colon but that does not fix anything
The syntax for creating an NSDictionary using object literals is:
dictionary = #{key:value}
(and optionally, it can contain multiple key/value pairs separated by commas, but never mind that right now.)
Where "key" and "value" are both NSObjects.
Your line that is throwing the error only contains 1 thing. The contents of a the string in dictString2 has nothing to do with it.
It looks to me like you are trying to build a JSON string manually. Don't do that. Use NSJSONSerialization. That class has a method dataWithJSONObject that takes an NSObject as input and returns NSData containing the JSON string. That's how you should be creating JSON output.
Creating an NSDictionary with values that may be null:
NSDictionary *dict = #{
#"key" : value ?: [NSNull null],
};
When serializing a dictionary, NSNulls are translated to null in the JSON.
If you want to exclude such keys completely, instead of having them with a null value, you'll have to do more work. The simplest is to use an NSMutableDictionary and test each value before adding it.

xcode - compare result of NSDictionary object with integer

i have simple question about how to compare result of NSDictionary with integer
i print on log the data in key result its equal 0
log
2014-09-17 10:25:42.848 School Link Version 2[1027:60b] the result are 0
but when i compare it , its dosnt work
i wish know why , its simple compare
-(void) didFinish:(NSMutableData *)data{
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"the result are %#",json[#"result"]);
if (json[#"result"] == 0) {
[[self _delegate] didFailWithMessage:json[#"message"]];
return;
}
}
You compare string to int, you need to convert string value to int:
if ([json[#"result"] intValue] == 0) {
First, your code will crash if the server didn't give you a dictionary, but an array. Since it is out of your control what the server sends, your app can crash at any time. You can check that you received a dictionary by writing
if (! [json isKindOfClass:[NSDictionary class]]) { /* Handle error */ }
Once you know it's a dictionary, you need to check what kind of item you actually expect. Do you expect a string, an integer, a decimal number? You should have a spec for the JSON data. If you are not sure, or want to be flexible, both strings and numbers produce an object that will support messages like "doubleValue", "integerValue" etc. Extract the item and assign it to a pointer of a class supporting "doubleValue", for example.
NSString* result = json [#"result"];
if (! [result respondsToSelector:#selector (doubleValue)]) { /* Handle error */ }
Now you can check whether the doubleValue is equal to 0.
if ([result doubleValue] == 0.0) { ... }
intValue will fail if the value is a large integer.
integerValue is slightly better but will fail if the value is for example the number 0.3 or the string "0.3". Everything will fail if the result is a string with the contents "Hello". You also should figure out what you want to do if result == nil (there was no key "result") or if result == [NSNull null] (there was a key:value pair "result": null)
The code that you actually wrote compared the object stored at the key "result" with 0, that is with a nil pointer. The object will be nil if the JSON data doesn't contain anything under that key. For example,
if (json [#"RandomNonsenseKey"] == 0)
will most likely succeed (unless your server sent data with a key RandomNonsenseKey).

can I switch NSString

I want to switch NSString in XmlParser because if there are 15 or more web-service then every time the loop check for correct element in IF..ELSE.That I don't want to make processor busy..
I have searched a lot and found using enum I can switch NSString but no luck ..
I have tried each possibilities,but some where i am making mistake.
Please help to solve this big problem for me.
Here I have declare my enum:
Here in "elementName" I am getting Exact value as declared in enum:
But instead of 1, I am getting wrong value Like 202896536:
You cant do it by creating enum. You must need to compare the string.
if([elementName isEqualToString:#"UserLoginComplexType"])
//Do something...
You can not cast a string to ENUM value, you will need to parse it, ENUM values are integers not strings.
You will have to use an if statement.
You could use a helper method:
WebServiceList.h
typedef NS_ENUM(NSUInteger, WebServiceList) {
WebServiceListNone = 0,
UserLoginComplexType = 1,
RegisterUserResult = 2,
RecoverPasswordResult = 3,
....
};
FOUNDATION_EXTERN WebServiceList WebServiceListForString(NSString *string);
WebServiceList.m
WebServiceList WebServiceListForString(NSString *string) {
WebServiceList list = WebServiceListNone;
if (![type isKindOfClass:[NSString class]]) {
return CallRecordTypeNone;
}
else if ([string isEqualToString:#"UserLoginComplexType"] {
list = UserLoginComplexType;
}
else if ([string isEqualToString:#"UserLoginComplexType"]) {
list = UserLoginComplexType;
}
else .....
return list;
}
As seen in your commented codes, you're parsing a XML and saving in a NSMutableArray named arrProductList in App Delegate.
After finishing the parsing of XML, the variable should contain the data in array. You should look into the variable & fetch the corresponding value. Since you didn't post any further details about parsing / XML structure, I'm unable to write some codes related to result fetching.
For easy readability and to avoid lots of if-else statements, I like to do mine as a dictionary:
(also makes it easy to update in the future when you add more to your enum)
NSString* elementName = ...;
// Default value
WebServiceList value = UserLoginComplexType;
NSDictionary* stringToEnum = #{#"UserLoginComplexType":#(UserLoginComplexType),
#"RegisterUserResult":#(RegisterUserResult),
#"RecoverPasswordResult":#(RecoverPasswordResult)};
NSNumber* enumValue = stringToEnum[elementName];
if(enumValue != nil)
value = (WebServiceList)enumValue.integerValue;

how to handle boolean value with AFNetworking parsing JSON

I have some JSON that comes back like this:
"items":[
{
"has_instore_image": false
}
]
If I output the value like this:
NSLog(#"has_instore_image val: %#", [item objectForKey:#"has_instore_image"]);
I get
has_instore_image val: 0
but if I test like this:
if([item objectForKey:#"has_instore_image"]==0){
NSLog(#"no, there is not an instore image");
}else{
...
It always goes to the else statement... hmmm.. How would you suggest I get the BOOL value and test? I've read through the BOOL questions here and am just confused this is not working as I'd anticipate.
thx
NSDictionary's instance method objectForKey returns an id, not a primitive value.
If it's a boolean, int, float, etc number-like value in JSON, it will serialized to an NSNumber by Apple's NSJSONSerialization class and most/all other common JSON parsers in iOS.
If you want to get the BOOL value out of it, you can do something like this:
BOOL has_instore_image = [[item objectForKey:#"has_instore_image"] boolValue];
You are comparing a pointer with an integer here
[item objectForKey:#"has_instore_image"]==0
You should use
[item objectForKey:#"has_instore_image"].integerValue==0
Also note that a BOOL of NO equals 0.
The NSLogstatement in your code prints a 0, but only because if you give NSLogan object as parameter, the objects descriptionis called.
i will suggest to hold these id type (returned from dictionary) to NSNumber .
NSNumber *boolNum=(NSNumber*)[item objectForKey:#"has_instore_image"];
after that you can get bool value from boolNum
[boolNum boolValue]
try this
if([boolNum boolValue]==NO){
NSLog(#"no, there is not an instore image");
}else
{
}

Resources