I have a simple JSON array that is returned from a zip code passed to a third party service.
http://api.geonames.org/findNearbyPostalCodes?postalcode=94115&country=US&radius=5&username=frequentz
I get an unknown error when trying to deserialize the results and I'm not sure what is going wrong.
Here is my connectionDidFinishLoading method, which fires as anticiapated but always fails...and I get the error in the last else if. Ideas?
-(void) connectionDidFinishLoading:(NSURLConnection *)connection {
NSLog(#"connectionDidFinishLoading...");
self.zipCodes = [NSMutableArray array];
NSError *error = nil;
id jsonObject = [NSJSONSerialization JSONObjectWithData:receivedData options:NSJSONReadingAllowFragments error:&error];
if (jsonObject != nil && error == nil) {
NSLog(#"Successfully deserialized...");
if ([jsonObject isKindOfClass:[NSDictionary class]]) {
NSDictionary *deserializedDictionary = (NSDictionary *)jsonObject;
NSLog(#"Deserialized JSON Dictionary = %#", deserializedDictionary);
for (NSDictionary *item in jsonObject) {
NSString *city = [item objectForKey:#"adminName2"];
NSString *stateAbbreviation = [item objectForKey:#"adminCode1"];
NSString *postalCode = [item objectForKey:#"postalCode"];
NSString *distance = [item objectForKey:#"distance"];
NSString *country = [item objectForKey:#"country"];
NSString *stateName = [item objectForKey:#"stateName"];
ZipCodes *zipCode = [[ZipCodes alloc] initWithName:city stateAbbrev:stateAbbreviation postalCode:postalCode distanceFromGPSZip:distance country:country stateFullName:stateName];
[self.zipCodes addObject:zipCode];
}
}
else if ([jsonObject isKindOfClass:[NSArray class]]){
NSArray *deserializedArray = (NSArray *)jsonObject;
NSLog(#"Deserialized JSON Array = %#", deserializedArray);
}
else {
/* Some other object was returned. We don't know how to deal
with this situation as the deserializer returns only dictionaries or arrays */
}
}
else if (error != nil){
NSLog(#"An error happened while deserializing the JSON data.");
}
}
I think you're using the wrong service --it should be ...findNearbyPostalCodesJSON.... To use the JSON service as far as I can tell from their website. This is their example URL:
http://api.geonames.org/findNearbyPostalCodesJSON?postalcode=8775&country=CH&radius=10&username=demo
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'm new with JSon stuff, so bear with me. I'm deserializing a JSon from a URL, and everything is fine until I try to separate the objects within. The app crashes and I get an error that I don't understand. Maybe you can help me see what I'm missing.
[NSURLConnection sendAsynchronousRequest:urlRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
if ([data length]>0 && error == nil) {
id jsonObject = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
if (jsonObject != nil && error == nil) {
if ([jsonObject isKindOfClass:[NSDictionary class]]) {
NSDictionary *deserializedDictionary = [[NSDictionary alloc] init];
deserializedDictionary = jsonObject;
NSLog(#"Deserialized Dictionary = %#",deserializedDictionary);
/*
LOG: Deserialized Dictionary = { d = "[{\"unit\":\"P101\",\"price\":36.0000,\"stat\":\"process\",\"type\":\"P12\"},{\"unit\":\"P102\",\"price\":38.0000,\"stat\":\"process\",\"type\":\"P13\"},..}
*/
NSMutableArray *dicts = [[NSMutableArray alloc] init];
dicts = (NSMutableArray *)deserializedDictionary[#"d"];
NSLog(#"Print dicts: %#",dicts);
/*
LOG: Print dicts: [{"unit":"P101","price":36.0000,"stat":"process","type":"P12"},{"unit":"P102","price":38.0000,"stat":"process","type":"P13"},..]
*/
NSLog(#"%#",NSStringFromClass([dicts class]));
//LOG: __NSCFString
NSMutableDictionary *myDict = [[NSMutableDictionary alloc] init];
for (myDict in dicts)
{
NSLog(#"myDict objectForKey: id-> %# myDict objectForKey: result-> %#",[myDict objectForKey:#"unit"],[myDict objectForKey:#"result"]);
}
}
And then I get this error:
[__NSCFString countByEnumeratingWithState:objects:count:]: unrecognized selector sent to instance 0x7fe97b327790
2016-03-08 11:29:12.946 Poop[49680:5673839] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFString countByEnumeratingWithState:objects:count:]: unrecognized selector sent to instance 0x7fe97b327790'
Help, please?
From your code and log i can understand that actual issue is on your server side as from these lines
NSMutableArray *dicts = [[NSMutableArray alloc] init];
dicts = (NSMutableArray *)deserializedDictionary[#"d"];
NSLog(#"Print dicts: %#",dicts);
/*
LOG: Print dicts: [{"unit":"P101","price":36.0000,"stat":"process","type":"P12"},{"unit":"P102","price":38.0000,"stat":"process","type":"P13"},..]
*/
NSLog(#"%#",NSStringFromClass([dicts class]));
//LOG: __NSCFString
Log says your variable named dicts is of type NSCFString not NSMutableArray and NSString don't have keys and Enumeration can not run on NSString type of object.
Issue is with your API response, which is not returning correct JSON.
Solution is to change on your server side to return array or dictionary instead of string.
See the edited code:
[NSURLConnection sendAsynchronousRequest:urlRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
if ([data length]>0 && error == nil) {
id jsonObject = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
if (jsonObject != nil && error == nil) {
if ([jsonObject isKindOfClass:[NSDictionary class]]) {
NSDictionary *deserializedDictionary = [[NSDictionary alloc] init];
deserializedDictionary = jsonObject;
NSLog(#"Deserialized Dictionary = %#",deserializedDictionary);
/*
LOG: Deserialized Dictionary = { d = "[{\"unit\":\"P101\",\"price\":36.0000,\"stat\":\"process\",\"type\":\"P12\"},{\"unit\":\"P102\",\"price\":38.0000,\"stat\":\"process\",\"type\":\"P13\"},..}
*/
NSMutableArray *responseArray = [[NSMutableArray alloc] init];
responseArray = deserializedDictionary[#"d"];
NSLog(#"Print responseArray: %#",responseArray);
/*
LOG: Print dicts: [{"unit":"P101","price":36.0000,"stat":"process","type":"P12"},{"unit":"P102","price":38.0000,"stat":"process","type":"P13"},..]
*/
NSLog(#"%#",NSStringFromClass([responseArray class]));
//LOG: __NSCFString
//The correct way of fast enumeration.
for (NSMutableDictionary *myDict in dicts)
{
NSLog(#"myDict objectForKey: id-> %# myDict objectForKey: result-> %#",[myDict objectForKey:#"id"],[myDict objectForKey:#"result"]);
}
}
Also, you should always check the existence of the key in the dictionary for each time you fetch the value from the dictionary, for this you can add this method in your HelperClass,
//Check is key exist in the dictionary
+(BOOL)validateKeyValueForData:(id)dataValue {
if([dataValue isEqual:[NSNull null]] || dataValue == nil)
{
return NO;
}
if([dataValue isKindOfClass:[NSArray class]] || [dataValue isKindOfClass:[NSMutableArray class]])
{
if([dataValue isEqual:[NSNull null]] || dataValue == nil || [dataValue count] <= 0)
{
return NO;
}
}
else
if([dataValue isKindOfClass:[NSDictionary class]] || [dataValue isKindOfClass:[NSMutableDictionary class]])
{
if([dataValue isEqual:[NSNull null]] || dataValue == nil || [dataValue count] <= 0)
{
return NO;
}
}
else if ([dataValue isKindOfClass:[NSString class]] || [dataValue isKindOfClass:[NSMutableString class]])
{
if([dataValue isEqual:[NSNull null]] || dataValue == nil || [dataValue length] <= 0)
{
return NO;
}
}
return YES;
}
And use this as
for (NSMutableDictionary *myDict in dicts)
{
//This way you make sure that the value for the specified key is exist in the dictionary.
if ([HelperClass validateKeyValueForData:myDict[#"id"]] && [HelperClass validateKeyValueForData:myDict[#"result"]]) {
NSLog(#"myDict objectForKey: id-> %# myDict objectForKey: result-> %#",[myDict objectForKey:#"id"],[myDict objectForKey:#"result"]);
}
}
It looks like dicts object is somehow being instantiated as a string. Take a look at the raw NSData, before it is parsed into a JSON object:
NSString *rawResponse = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"Raw data: %#", rawResponse);
If you set a general breakpoint to catch all exceptions, it might stop where you are trying to iterate through your dicts array. You can do this by:
Select the Breakpoints Navigator
Click the '+' button in the lower left
Select 'Add Exception Breakpoint...'
In the popup options menu, select the Break 'On Catch' option
I have managed to extract the following array (which I am dumping to console) from some json. How can I get and print out the value for one of the elements, i.e. task?
Objective-C:
NSArray *array = [dict objectForKey:#"row"];
NSLog(#"array is: %#",array);
Console output:
array is: {
0 = 1;
1 = "send email";
2 = "with attachment";
ltask = "with attachment";
task = "send email";
userid = 1;
}
array looks like it is actually an NSDictionary, so reference the key to get the value for it.
NSLog(#"Task: %#", array[#"task"]);
the variable array doesn't seem to be NSArray . Does this work for you?
id array = [dict objectForKey:#"row"];
if([array isKindOfClass:[NSDictionary class]]){
NSLog(#"Value of task %#",array[#"task"]);
}
From the log, it looks like the output is an NSDictionary object, so to get the value of task key just do this
NSDictionary *myDict = dict[#"row"];
NSString *task = myDict[#"task"];
NSLog(#"task = %#", task);
if you want to confirm just check the class type using isKindOfClass: method
if([dict[#"row"] isKindOfClass:[NSDictionary class]]) {
NSDictionary *myDict = dict[#"row"];
NSString *task = myDict[#"task"];
NSLog(#"task = %#", task);
} else if([dict[#"row"] isKindOfClass:[NSArray class]]) {
NSArray *myArray = dict[#"row"];
NSDictionary *myDict = myArray[0];
NSString *task = myDict[#"task"];
NSLog(#"task = %#", task);
}
try
if ([[dictionary allKeys] containsObject:#"row"]) {
NSObject *objRow = dictionary[#"row"];
if(objRow){
if([objRow isKindOfClass:[NSArray class]]){
NSArray *arr = (NSArray *)objRow;
....
}
if([objRow isKindOfClass:[NSDictionary class]]){
NSDictionary *dic = (NSDictionary *)objRow;
....
}
}
}
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;
}
I am trying to use the data which I read from a text file in objective c. The data I read from the text file is:
{"aps":{"alert":"Test 1!","sound":"beep.wav","badge":5,"Type":"Banking"},"acme1":"bar","acme2":42}|{"aps":{"alert":"Test 2!","sound":"beep.wav","badge":5,"Type":"Banking"},"acme1":"bar","acme2":42}|{"aps":{"alert":"Test 3!","sound":"beep.wav","badge":5,"Type":"Banking"},"acme1":"bar","acme2":42}|{"aps":{"alert":"Test 4!","sound":"beep.wav","badge":5,"Type":"Banking"},"acme1":"bar","acme2":42}|{"aps":{"alert":"Test 5!","sound":"beep.wav","badge":5,"Type":"Banking"},"acme1":"bar","acme2":42}
Once read, I split the file into an array with a delimiter of "|". I then want to further separate it into 3 different arrays: banking, fraud and investment based on the key "Type". However I cannot seem to reach parse the JSON string once I split it into the array. My view did load method is below:
- (void)viewDidLoad {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *fileName = [NSString stringWithFormat:#"%#/AccountNotifications.txt", documentsDirectory];
NSString *fileContents = [[NSString alloc] initWithContentsOfFile:fileName usedEncoding:nil error:nil];
NSArray *fileData = [fileContents componentsSeparatedByString:#"|"];
if (fileContents != NULL)
{
bankingNotifications = [[NSMutableArray alloc] init];
fraudNotifications = [[NSMutableArray alloc] init];
investmentNotifications = [[NSMutableArray alloc] init];
for (i = 0; i < [fileData count]; i++)
{
NSString *notification = fileData[i];
NSDictionary *json = [notification JSONValue];
NSArray *items = [json valueForKeyPath:#"aps"];
if ([[[items objectAtIndex:i] objectForKey:#"Type"] isEqual: #"Banking"])
{
[bankingNotifications addObject:fileData[i]];
NSLog(#"Added object to banking array");
}
if ([[[items objectAtIndex:i] objectForKey:#"Type"] isEqual: #"Fraud"])
{
[fraudNotifications addObject:fileData[i]];
NSLog(#"Added object to fraud array");
}
if ([[[items objectAtIndex:i] objectForKey:#"Type"] isEqual: #"Investment"])
{
[investmentNotifications addObject:fileData[i]];
NSLog(#"Added object to investment array");
}
} }
There is an error with these three lines:
NSString *notification = fileData[i];
NSDictionary *json = [notification JSONValue];
NSArray *items = [json valueForKeyPath:#"aps"];
Could you please help me parse the JSON strings into the three mutable arrays? The error I am getting is:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSDictionaryM objectAtIndex:]: unrecognized selector sent to instance 0x1d59db30'
If you create the text file yourself I would suggest you create a valid json object (as your data looks like it is supposed to be json) to keep your data nice and clean. similar to this:
{"aps":[{"type":"Banking","badge":5},{"Type":"Fraud","badge":12}]}
Then you can do following (this code is not tested, it can be that you have to amend it a bit) but i hope you'll get an idea :)
NSError* error = nil;
NSDictionary* dict = nil;
//serialising the jsonobject to a dictionary
dict = [NSJSONSerialization JSONObjectWithData:fileContents
options:kNilOptions
error:&error];
bankingNotifications = [[NSMutableArray alloc] init];
fraudNotifications = [[NSMutableArray alloc] init];
investmentNotifications = [[NSMutableArray alloc] init];
if (dict) {
NSArray *dataArray = [dict objectForKey:#"aps"];
NSDictionary* siteData = nil;
NSEnumerator* resultsEnum = [dataArray objectEnumerator];
while (siteData = [resultsEnum nextObject])
{
//
if( [[siteData objectForKey:#"Type"] isEqualToString: #"Banking"]) {
[bankingNotifications addObject:notification];
NSLog(#"Added object to banking array");
} else if ([[siteData objectForKey:#"Type"] isEqualToString: #"Fraud"])
{
[fraudNotifications addObject:notification];
NSLog(#"Added object to fraud array");
}
else if ([[siteData objectForKey:#"Type"] isEqualToString: #"Investment"])
{
[investmentNotifications addObject:notification];
NSLog(#"Added object to investment array");
}
}
}
The value for Key "aps" is a dictionary.
NSDictionary *item = [json valueForKeyPath:#"aps"];
if ([[item objectForKey:#"Type"] isEqualToString: #"Banking"])
{
[bankingNotifications addObject:notification];
NSLog(#"Added object to banking array");
}
else if ([[item objectForKey:#"Type"] isEqualToString: #"Fraud"])
{
[fraudNotifications addObject:notification];
NSLog(#"Added object to fraud array");
}
else if ([[item objectForKey:#"Type"] isEqualToString: #"Investment"])
{
[investmentNotifications addObject:notification];
NSLog(#"Added object to investment array");
}