I have a problem to communicate with a server. The webserver expects all parameters in the JSON object to be a string. So every number and every boolean in every container needs to be a string.
For my example I have a NSDictionary full of key values (values are all kinds of types - numbers, arrays etc.). For example:
{
"AnExampleNumber":7e062fa,
"AnExampleBoolean":0,
"AnExampleArrayOfNumber":[17,4,8]
}
Has to become:
{
"AnExampleNumber":"7e062fa",
"AnExampleBoolean":"0",
"AnExampleArrayOfNumber":["17","4","8"]
}
I tried the standard NSJSONSerializer but it doesn't give me any option to do what I need. I then tried to transform everything in the dictionary manually to be a string but that seems to be overhead. Does anyone have hint for me? Maybe a serializer that does just that or a function to convert any objects in a container to strings?
This is one way you could do it. It's non-optimized and has no error handling. It only supports the kinds of objects that NSJSONSerializer supports.
#import <Foundation/Foundation.h>
#interface NSObject(SPWKStringify)
- (id)spwk_stringify;
#end
#implementation NSObject(SPWKStringify)
- (id)spwk_stringify
{
if ([self isKindOfClass:[NSDictionary class]]) {
NSDictionary *dict = (NSDictionary *)self;
NSMutableDictionary *newDict = [[NSMutableDictionary alloc] init];
for (NSString *key in [dict allKeys]) {
newDict[key] = [dict[key] spwk_stringify];
}
return newDict;
} else if ([self isKindOfClass:[NSArray class]]) {
NSMutableArray *newArray = [[NSMutableArray alloc] init];
for (id value in ((NSArray *)self)) {
[newArray addObject:[value spwk_stringify]];
}
return newArray;
} else if (self == [NSNull null]) {
return #"null"; // representing null as a string doesn't make much sense
} else if ([self isKindOfClass:[NSString class]]) {
return self;
} else if ([self isKindOfClass:[NSNumber class]]) {
return [((NSNumber *)self) stringValue];
}
return nil;
}
#end
int main(int argc, char *argv[]) {
#autoreleasepool {
NSDictionary *dict = #{
#"AnExampleNumber": #1234567,
#"AnExampleBoolean": #NO,
#"AnExampleNull": [NSNull null],
#"AnExampleArrayOfNumber": #[#17, #4, #8],
#"AnExampleDictionary": #{#"innerKey": #[#55, #{#"anotherDict": #[#"foo", #[#1, #2, #"three"]]}]}
};
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:[dict spwk_stringify] options:NSJSONWritingPrettyPrinted error:NULL];
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
NSLog(#"result: %#", jsonString);
}
}
The output will be:
result: {
"AnExampleNumber" : "1234567",
"AnExampleNull" : "null",
"AnExampleDictionary" : {
"innerKey" : [
"55",
{
"anotherDict" : [
"foo",
[
"1",
"2",
"three"
]
]
}
]
},
"AnExampleBoolean" : "0",
"AnExampleArrayOfNumber" : [
"17",
"4",
"8"
]
}
Note: Please keep in mind that turning [NSNull null] into a string doesn't make any sense and might actually be misleading and dangerous.
Enjoy.
(I assume you mean NSJSONSerializer, not NSSerializer.)
I doubt you'll find a pre-rolled solution to this. It's not a general problem. As you note, this is incorrect JSON, so JSON serializers shouldn't do it.
The best solution IMO is just write the code to transform your NSDictionary into another NSDictionary that is in the form you want. If you really want to make it a generic solution, I suspect that a custom NSDictionary walker with isKindOfClass: is your best bet. Something like this should work:
NSDictionary *myStringDictForDict(NSDictionary *dict); // forward decl if needed
NSArray *myStringArrayForArray(NSArray *array) {
NSMutableArray *result = [NSMutableArray new];
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([obj isKindOfClass:[NSArray class]]) {
[result addObject:myStringArrayForArray(obj)];
} else if ([obj isKindOfClass:[NSDictionary class]]) {
[result addObject:myStringDictForDict(obj)];
} else {
[result addObject:[obj description]];
}
}];
return result;
}
NSDictionary *myStringDictForDict(NSDictionary *dict) {
NSMutableDictionary *result = [NSMutableDictionary new];
[dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if ([obj isKindOfClass:[NSArray class]]) {
result[key] = myStringArrayForArray(obj);
} else if ([obj isKindOfClass:[NSDictionary class]]) {
result[key] = myStringDictForDict(obj);
} else {
result[key] = [obj description];
}
}];
return result;
}
Related
I am trying to set my text string to a URL. The following code works, but I feel like I can refactor it to make it look neater.
NSString *text = #“”;
id json = [NSJSONSerialization JSONObjectWithData:[data dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
if ([json isKindOfClass:[NSDictionary class]]) {
id data = json[#“data”];
if ([data isKindOfClass:[NSDictionary class]]) {
id value = data[#"value"];
if ([value isKindOfClass:[NSArray class]]) {
id url = [value valueForKey:#"url"];
if ([url isKindOfClass:[NSString class]]) {
text = url;
}
}
}
}
So far it has the whole "mountain of doom" going on and I want to know how can I check if the object type is correct without using so many if statements. Any tips or suggestions are appreciated.
Edit: This is the lite version of my code, but the concept is the same.
In my opinion, there are 2 ways to make it look neater and ignore if-else-nesting-hell.
Using return.
NSString *text = #“”;
id json = [NSJSONSerialization JSONObjectWithData:[data dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
if (![json isKindOfClass:[NSDictionary class]]) {
return;
}
id data = json[#“data”];
if (![data isKindOfClass:[NSDictionary class]]) {
return;
}
id value = data[#"value"];
if (![value isKindOfClass:[NSArray class]]) {
return;
}
id url = [value valueForKey:#"url"];
if (![url isKindOfClass:[NSString class]]) {
return;
}
text = url;
Create a generic method which checks kind of class and return a safe value
- (id)safeValueFromObject:(id)object forKey:(NSString *)key class:(Class)valueClass {
if (![object respondsToSelector:#selector(valueForKey:)]) {
return [[valueClass alloc] init];
}
id result = [object valueForKey:key];
return [result isKindOfClass:valueClass] ? result : [[valueClass alloc] init];
}
Use
NSString *text = #"";
id json = [NSJSONSerialization JSONObjectWithData:[data dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
id data = [self safeValueFromObject:json forKey:#"data" class:NSDictionary.class];
id value = [self safeValueFromObject:data forKey:#"value" class:NSArray.class];
id url = [self safeValueFromObject:value forKey:#"url" class:NSString.class];
text = url;
I want to change the Dictionary's all value to String, how to do with it?
Such as:
{ #"a":"a",
#"b":2,
#"c":{
#"c1":3,
#"c2":4
}
}
I want convert to :
{ #"a":"a",
#"b":"2",
#"c":{
#"c1":"3",
#"c2":"4"
}
}
How to do with it? I think all the day.
If I use below method to traverse the dictionary values:
NSArray *valueList = [dictionary allValues];
for (NSString * value in valueList) {
// change the value to String
}
If the value is a dictionary, how about it?
So, someone can help with that?
You could do this with a recursive method, it changes all NSNumber values to NSString and calls itself for nested dictionaries. Since a dictionary cannot be mutated while being enumerated a new dictionary is created and populated:
- (void)changeValuesOf:(NSDictionary *)dictionary result:(NSMutableDictionary *)result
{
for (NSString *key in dictionary) {
id value = dictionary[key];
if ([value isKindOfClass: [NSDictionary class]]) {
NSMutableDictionary * subDict = [NSMutableDictionary dictionary];
result[key] = subDict;
[self changeValuesOf:value result:subDict];
} else if ([value isKindOfClass: [NSNumber class]]) {
result[key] = [NSString stringWithFormat:#"%#", value];
} else {
result[key] = value;
}
}
}
NSDictionary *dictionary = #{#"a": #"a", # "b":#2, #"c": #{#"c1": #3, #"c2":#4 }};
NSMutableDictionary *result = [NSMutableDictionary dictionary];
[self changeValuesOf:dictionary result:result];
NSLog(#"%#", result);
You can create category for a dictionary and add method some like stringValueForKey:.
The realisation can be something like this:
- (NSString)stringValueForKey:(NSString*)key
{
id value = self[key];
if( [value respondsToSelector:#selector(stringValue)])
return [value performSelector:#selector(stringValue)]
return nil;
}
I have 100 key and value in nsmutabledictornary and i want to check that any value have null or not. Do you have any short function or technique?
I don't want to multiple line code like check every key and value. Your answer would be appreciated.
This code will give you the set of keys which have (non)null values. You can't store actual nil values in a dictionary, so [NSNull null] is assumed. The predicate is trivially alterable to any other condition.
NSDictionary *d = #{ #"a" : #"1", #"b" : [NSNull null] };
NSSet *nullKeys = [d keysOfEntriesPassingTest:^BOOL(NSString *key, id obj, BOOL *stop) {
return [d[key] isKindOfClass:[NSNull class]];
}];
NSSet *nonnullKeys = [d keysOfEntriesPassingTest:^BOOL(NSString *key, id obj, BOOL *stop) {
return [d[key] isKindOfClass:[NSNull class]] == NO;
}];
From here, you can use the keys to generate a corresponding dictionary, if needed.
NSMutableDictionary *nonNullDict = [NSMutableDictionary dictionary];
[d enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
if ([nonnullKeys contains:key]) {
nonNullDict[key] = obj;
}
}];
If you don't need a separate list of keys, and just need the filtered dictionary, skip the first step and modify the second part to read as follows:
NSMutableDictionary *nonNullDict = [NSMutableDictionary dictionary];
[d enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
if ([obj isKindOfClass:[NSNull null]] == NO) {
nonNullDict[key] = obj;
}
}];
Write category on NSDictionary it will provide you null free dictionary. Here is the category I have written for myself.
code for .h file (interface)
#import <Foundation/Foundation.h>
#interface NSDictionary (CheckNull)
{
}
- (NSDictionary *)nullFreeDictionary;
#end
Code for .m file. (implementation)
#import "NSDictionary+CheckNull.h"
#implementation NSDictionary (CheckNull)
- (NSDictionary *) nullFreeDictionary
{
NSMutableDictionary *tempDictionary = [self mutableCopy];
for (NSString *key in tempDictionary.allKeys) {
NSString *value = [tempDictionary valueForKey:key];
if ([value isKindOfClass:[NSString class]]) {
if (value == (id)[NSNull null] || value == nil || value.length == 0) {
[tempDictionary setValue:#"" forKey:key];
}
}
}
return tempDictionary;
}
Call null free method on your dictionary using above category.
NSDictionary *dict = [dict nullFreeDictionary];
//To remove NULL from Dictionary
-(NSMutableDictionary *)removeNullFromDictionary : (NSMutableDictionary *)dict
{
// if (![dict isKindOfClass:[NSMutableDictionary class]])
// {
// }
dict = [[NSMutableDictionary alloc] initWithDictionary:dict];
for (NSString * key in [dict allKeys])
{
if ([dict[key] isKindOfClass:[NSNull class]])
{
[dict setValue:#"" forKey:key];
}
else if ([dict[key] isKindOfClass:[NSMutableDictionary class]]||[dict[key] isKindOfClass:[NSDictionary class]])
{
dict[key] = [self removeNullFromDictionary:[NSMutableDictionary dictionaryWithDictionary:dict[key]]];
}
else if ([dict[key] isKindOfClass:[NSMutableArray class]]||[dict[key] isKindOfClass:[NSArray class]])
{
dict[key] = [self removeNullFromArray:[NSMutableArray arrayWithArray:dict[key]]];
}
}
return dict;
}
//To remove NULL from Array
-(NSMutableArray *)removeNullFromArray : (NSMutableArray *)arr
{
// if (![arr respondsToSelector:#selector(addObject:)])
// {
// arr = [[NSMutableArray alloc] initWithArray:arr];
// }
arr = [[NSMutableArray alloc] initWithArray:arr];
for (int cnt = 0; cnt<[arr count]; cnt++)
{
if ([arr[cnt] isKindOfClass:[NSNull class]])
{
arr[cnt] = #"";
}
else if ([arr[cnt] isKindOfClass:[NSMutableDictionary class]]||[arr[cnt] isKindOfClass:[NSDictionary class]])
{
arr[cnt] = [self removeNullFromDictionary:[NSMutableDictionary dictionaryWithDictionary:arr[cnt]]];
}
else if ([arr[cnt] isKindOfClass:[NSMutableArray class]]||[arr[cnt] isKindOfClass:[NSArray class]])
{
arr[cnt] = [self removeNullFromArray:[NSMutableArray arrayWithArray:arr[cnt]]];
}
}
return arr;
}
It seems that since XCode 6.1, the iPhone 5S, iPhone 6 and iPhone 6+ simulators (all 64-bit) all return data from the following system method differently (keys are ordered differently) than their 32-bit simulator counterparts (e.g. iPhone 5 simulator)
+ (NSData *)dataWithJSONObject:(id)obj options:(NSJSONWritingOptions)opt error:(NSError **)error;
This difference of key ordering caused a problem for me since we calculate the SHA1 of that JSON data (converted to NSString*) and use it for a validation test. Since the ordering changed, the SHA1 changed and the validation fails.
Simplified sample code (non-ARC) to get the SHA1 is below:
NSData* jsonData = [NSJSONSerialization dataWithJSONObject:dict
options:0
error:&error];
NSString * json = [[[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] autorelease];
NSString * sha1 = [MyUtils computeSHA1:json];
+(NSString*) computeSHA1:(NSString*)input
{
const char *cstr = [input cStringUsingEncoding:NSUTF8StringEncoding];
NSData *data = [NSData dataWithBytes:cstr length:input.length];
NSNumber* dataLen = [NSNumber numberWithUnsignedInteger:data.length];
uint8_t digest[CC_SHA1_DIGEST_LENGTH];
CC_SHA1(data.bytes, dataLen.unsignedIntValue, digest);
NSMutableString* output = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
for(int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++)
[output appendFormat:#"%02x", digest[i]];
return output;
}
Apparently, this key ordering difference doesn't happen on the actual devices (previous behavior was preserved).
I also tried with the NSJSONWritingPrettyPrinted option but the JSON ordering is still inconsistent between simulators.
So, the question is: Does anyone have a recommendation on how to normalize such JSON data so as to not be susceptible to key ordering changes? Alternately, is there any way to get the previous (32-bit simulator) behavior?
Key ordering in dictionaries is not guaranteed. If you need them sorted, put them into an array and sort them.
The code below (non-ARC) worked for me to better canonicalize JSON output. The code assumes the class methods below are all in a class called MyUtils.
Simply pass the the NSDictionary to serialize into "canonicalized JSON" to canonicalJSONRepresentationWithDictionary:
The returned NSString* then contains serialized JSON that has the keys ordered lexicographically/alphabetically in a non-human readable format.
+(NSString *) canonicalJSONRepresentationWithDictionary:(NSDictionary *)dict
{
NSMutableString* json = [NSMutableString string];
[json appendString:#"{"];
NSArray* keys = [[dict allKeys] sortedArrayUsingComparator:^NSComparisonResult(NSString* a, NSString* b) {
return [a compare:b];
}];
for (int i = 0; i < keys.count; i++) {
NSString* key = keys[i];
[json appendFormat:#"\"%#\":", key];
if ([dict[key] isKindOfClass:[NSString class]]) {
[json appendFormat:#"\"%#\"", [MyUtils canonicalJSONRepresentationWithString:dict[key]]];
} else if ([dict[key] isKindOfClass:[NSDictionary class]]) {
[json appendString:[MyUtils canonicalJSONRepresentationWithDictionary:dict[key]]];
} else if ([dict[key] isKindOfClass:[NSArray class]]) {
[json appendString:[MyUtils canonicalJSONRepresentationWithArray:dict[key]]];
} else {
return nil;
}
if (i < keys.count - 1) {
[json appendString:#","];
}
}
[json appendString:#"}"];
return json;
}
+(NSString *) canonicalJSONRepresentationWithArray:(NSArray *) array
{
NSMutableString* json = [NSMutableString string];
[json appendString:#"["];
for (int i = 0; i < array.count; i++) {
if ([array[i] isKindOfClass:[NSString class]]) {
[json appendFormat:#"\"%#\"", [MyUtils canonicalJSONRepresentationWithString:array[i]]];
} else if ([array[i] isKindOfClass:[NSDictionary class]]) {
[json appendString:[MyUtils canonicalJSONRepresentationWithDictionary:array[i]]];
} else if ([array[i] isKindOfClass:[NSArray class]]) {
[json appendString:[MyUtils canonicalJSONRepresentationWithArray:array[i]]];
} else {
return nil;
}
if (i < array.count - 1) {
[json appendString:#","];
}
}
[json appendString:#"]"];
return json;
}
+(NSString *) canonicalJSONRepresentationWithString:(NSString *) string;
{
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:string, #"a", nil];
NSError * error;
NSData * jsonData = nil;
NSString * json = nil;
jsonData = [NSJSONSerialization dataWithJSONObject:dict
options:0
error:&error];
if (!jsonData) {
NSLog(#"Got an error serializing json: %#", error);
return nil;
} else {
json = [[[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] autorelease];
}
NSRange colonQuote = [json rangeOfString:#":\""];
NSRange lastQuote = [json rangeOfString:#"\"" options:NSBackwardsSearch];
NSRange range = NSMakeRange(colonQuote.location + 2, lastQuote.location - colonQuote.location - 2);
NSString* rc = [json substringWithRange:range];
return rc;
}
How can I loop through this part of the json [{first set of values},{data->children->data->body} in objective c?
Json is
[
{
"kind": "Listing"
},
{
"kind": "Listing",
"data": {
"children": [
{
"data": {
"body": "body1"
}
},
{
"data": {
"body": "body2"
}
}
]
}
}
]
My current code is
m_ArrList=[[NSMutableArray alloc]init];
NSDictionary *infomation = [self dictionaryWithContentsOfJSONString:#"surveyquestion.json"];
NSArray *array=[infomation objectForKey:#"data"];
int ndx;
NSLog(#"%#",array);
for (ndx = 0; ndx < [array count]; ndx++) {
NSDictionary *stream = (NSDictionary *)[array objectAtIndex:ndx];
NSArray *string=[stream valueForKey:#"children"];
//i am stuck here
}
What do I do at the "//i am stuck here" ?
You might need to add the values of #"children" dictionary in an array and then parse that array to get the data inside children
[childrenArray addObject:[stream objectForKey:#"children"]];
// finally parse childrenArray
// You Just need to Implement following Lines and you will get all the data for Key Body in children array
NSDictionary *infomation = [self dictionaryWithContentsOfJSONString:#"surveyquestion.json"];
NSArray *string= [[infomation objectForKey:#"data"] objectForKey:#"children"];
[string enumerateObjectsUsingBlock:^(id obj, NSUInteger ind, BOOL *stop){
NSLog(#"Body : %#",[[obj objectForKey:#"data"] objectForKey:#"body"]);
}];
Using NSJSONSerialization try to implement this. Here you need to pass NSString as jsonStr which you need to read from your file.
NSError *jsonError = nil;
id allValues = [NSJSONSerialization JSONObjectWithData:[jsonStr dataUsingEncoding:NSUTF8StringEncoding]
options:0
error:&jsonError];
if(jsonError!=nil)
NSLog(#"Json_Err: %#",jsonError);
NSArray *array=allValues;
for (int ndx = 0; ndx < [array count]; ndx++) {
NSDictionary *stream = (NSDictionary *)[array objectAtIndex:ndx];
NSLog(#"%#",[stream objectForKey:#"kind"]);
NSArray *child = [[stream objectForKey:#"data"] objectForKey:#"children"];
//i am stuck here
for(int i =0; i <[child count];i++)
{
NSDictionary *childData = (NSDictionary *)[child objectAtIndex:i];
//NSLog(#"%#",[childData objectForKey:#"data"]);
NSLog(#"%#",[[childData objectForKey:#"data"] objectForKey:#"body"]);
}
}