iPhone: Issue with parsing response dictionary: - iphone

I am getting server response and parsing as below for my synchronous request.
NSDictionary *responseDict = [NSJSONSerialization JSONObjectWithData:response options:0 error:&jsonError];
NSLog(#"responseDict: %#", responseDict);
The result is below:
responseDict: {
d = {
Datavalue = 1;
"__type" = "Response:#DummyAPI";
response = OK;
};
I am trying to parse the above result, if it is "OK" then I want to store the "Datavalue" somewhere..
I am trying like below,
-(void) handleResponse :(NSDictionary *) responsedata // it is being passed from caller
{
NSString* value = NULL;
for (id key in responsedata)
{
value = (NSString*)[responsedata objectForKey:#"response"];
if ( [value isEqualToString:#"OK"] )
{
NSLog(#"RESPONSE SUCCESS, need to store data value");
}
else
{
NSLog(#"INVALID RESPONSE");
}
}
}
But, it is always printing "INVALID RESPONSE", but I got response as "OK". What am I doing wrong here? Please advise!
Thank you!

You are missing a level of nesting in the dictionaries you received back. Your response shows that responseData contains a key called "d" whose value is another dictionary, and that dictionary is what has a key called "response"
Because you are working inside a loop, I will assume that your response can contain multiple values at the top level and not just "d", and that you are trying to loop through each of those. Based on that assumption you probably want something like this:
-(void) handleResponse :(NSDictionary *) responsedata // it is being passed from caller
{
NSString* value = NULL;
for (id key in responsedata)
{
NSDictionary *currentDict = (NSDictionary *) [responseData objectForKey:key];
value = (NSString*)[currentDict objectForKey:#"response"];
if ( [value isEqualToString:#"OK"] )
{
NSLog(#"RESPONSE SUCCESS, need to store data value");
}
else
{
NSLog(#"INVALID RESPONSE");
}
}
}

Seems weird to me and probably is not the problem but:
value = (NSString*)[responsedata objectForKey:#"response"];
if ( [value isEqualToString:#"OK"] ){
NSLog(#"RESPONSE SUCCESS, need to store data value");
}
else{
NSLog(#"INVALID RESPONSE");
}
That shouldn't been done inside a for loop. Just try that code outside the for loop, it should work. Other thing you might want to try doing is calling the isKindOfClass method over the value for #"response" and you should get something saying it is a string.

Related

bool for key in NSMutableArray

I have a code like that
if ([dataArray valueForKey:#"success"]) {
[self.feedsArray addObjectsFromArray:dataArray];
NSLog(#"self.feedsArray: %#",self.feedsArray);
} else {
NSLog(#"no feed found ");
}
dataArray is a NSMutableArray which ultimately contains a JSON Dictionary.
but I am getting the same console output independent of success either TRUE or FALSE, but my console output is always same.my console output is:
for FALSE or NO:
self.feedsArray: (
{
action = register;
message = "Invalid parameters";
success = 0;
}
)
and for TRUE or YES:
self.feedsArray: (
{
action = register;
message = "valid parameters";
success = 1;
}
)
in both cases if part is executed.
in NSUserDefaults there is a method boolForKey but how to do this in case of NSMutableArray.
You need to read the fine print for [NSArray valueForKey:], specifically:
Returns an array containing the results of invoking valueForKey: using
key on each of the array's objects.
and:
The returned array contains NSNull elements for each object that
returns nil.
So if the array contains, say, 3 objects and none of them have a success key then you will get an array of 3 NSNull objects returned.
Therefore the if statement will fire whenever dataArray is non-empty, which is obviously not what you intended.
You should check the contents of the returned array:
BOOL succeeded = NO;
NSArray *results = [dataArray valueForKey:#"success"];
for (NSObject *obj in results) {
succeeded = [obj isKindOfClass:[NSNumber class]] && [(NSNumber *)obj boolValue];
if (succeeded)
break;
}
if (succeeded) {
[self.feedsArray addObjectsFromArray:dataArray];
NSLog(#"self.feedsArray: %#",self.feedsArray);
} else {
NSLog(#"no feed found ");
}
You can do this in simple way:
What i see in your response json value is, you have dictionary in dataArray at index 0
NSMutableDictionary *responseDict = [dataArray objectAtIndex:0];
if([[responseDict objectForKey:#"success"] boolValue])
{
NSLog(#"Success: 1");
}
{
NSLog(#"Success: 0");
}
Use index instead of key for an array.
NSDictionary dictionary = (NSDictionary *)dataArray[0];
if ([(NSNumber *)[dictionary objectForKey:#"success"] boolValue]) {
// ...
}
otherwise use if([[[dataArray objectAtIndex:0] valueForKey:#"success"] isEqualToString:#"1"])
An array does not store keys, the only way to access items in an array is by index.
You should be using an NSDictionary/NSMutableDictionary instead. If you want to use a bool store it as a NSNumber, [NSNumber numberWithBool:YES] and then use the instance method valueForBool to read it back.
Try this
if ([[dataArray valueForKey:#"success"]isEqualToString:#"1"]) {
[self.feedsArray addObjectsFromArray:dataArray];
NSLog(#"self.feedsArray: %#",self.feedsArray);
}
else {
NSLog(#"no feed found ");
}
It 'll work out.
use this if you want bool value
if([[dataArray valueForKey:#"success"] boolValue])
{
//i.e success is true
}
if response contains array of dictionaries then we can use loop and check condition,
here i is index variable of array,
if([[[dataArray objectAtIndex:i] objectForKey:#"success"] boolValue])
{
// success is true ,
}
Replace you code line
if ([dataArray valueForKey:#"success"]) {
}
with
if ([[dataArray valueForKey:#"success"] integerValue]) {
}
Hope it will work for you.
its working with replacing the line with
if ([[[dataArray objectAtIndex:0] valueForKey:#"success"] boolValue])

Detect Missing JSON Keys With Nested Objects

Currently I am in the process of working with an API that is still in development. Due to this, the keys in the response are still changing. I have successfully been able to retrieve and parse the JSON data from the API into an NSDictionary, and then use this NSDictionary to map the values into custom objects. The approach I am using is the following
-(id)initWithDictionary:(NSDictionary*)dictionary
{
if(self = [super init]){
_ID = [dictionary valueForKey:kKEY_ID];
_name = [dictionary valueForKey:kKEY_NAME];
_nestedObject = [[NestedObject alloc]initWithDictionary:[dictionary valueForKey:kKEY_NESTED_OBJECT]];
//etc...
}
return self
}
Each nested object also contains the same parsing structure.
This works fine except for when the API changes. When something does change, required values do not exist and this causes unexpected behavior or crashes.
Ideally, if one of the keys change, I would like to produce a NSError that I can use to print the value that has changed helping me more quickly find the change and rectify it.
The only alternative approach that I have currently been able to come up with I feel is messy and unmaintainable.
-(id)initWithDictionary:(NSDictionary*)dictionary andError:(NSError**)error
{
if(self = [super init]){
BOOL _parsedSuccessfully = TRUE;
if (_parsedSuccessfully) {
_ID = [dictionary valueForKey: kKEY_ID];
if (!_ID){
_parsedSuccessfully = FALSE;
*error = [NSError parsingErrorFromKey: kKEY_ID];
}
}
if (_parsedSuccessfully) {
_name = [dictionary valueForKey: kKEY_NAME];
if (!_name){
_parsedSuccessfully = FALSE;
*error = [NSError parsingErrorFromKey: kKEY_NAME];
}
}
if (_parsedSuccessfully) {
_nestedObject = [[NestedObject alloc]initWithDictionary:[dictionary valueForKey:kKEY_NESTED_OBJECT]];
if (!_nestedObject){
_parsedSuccessfully = FALSE;
*error = [NSError parsingErrorFromKey: kKEY_NESTED_OBJECT];
}
}
//etc...
if (!_parsedSuccessfully) {
return nil;
}
}
return self
}
I was wondering if anyone else had any other better approaches that preferably uses much less duplication.
Any help would be greatly appreciated.
Add an isValid method to your object, which can be used in any situation, not just when initialised from the JSON dictionary.
- (BOOL)isValid:(NSError **)error {
#define CHECK_NOT_NULL(x, key) if (!x) { \
if (error != NULL) \
*error = [NSError parsingErrorFromKey:key]; \
return NO; \
}
#define CHECK_NOT_EMPTY(x, key) if (!x || ![x length]) { \
if (error != NULL) \
*error = [NSError parsingErrorFromKey:key]; \
return NO; \
}
CHECK_NOT_NULL(_ID, kKEY_ID);
CHECK_NOT_EMPTY(_name, kKEY_NAME);
// etc.
return YES;
#undef CHECK_NOT_NULL
#undef CHECK_NOT_EMPTY
}
And then use this in your init method:
- (id)initWithDictionary:(NSDictionary*)dictionary andError:(NSError**)error
{
if (self = [super init]) {
_ID = [dictionary valueForKey: kKEY_ID];
_name = [dictionary valueForKey: kKEY_NAME];
// etc.
if (![self isValid:error]) {
self = nil; // Assuming ARC
}
}
return self;
}
If you create an array of your keys then you can run your check in a loop so you only have one copy of the loop.
Again, using the array you could get all of the keys from the dictionary and remove them from each other. One way will give you new keys and the other way will give you the missing keys.

Parse json_encoded data into an array to be used for a UIPickerView - Xcode

I'm looking to populate an array to be used in a pickerview. The data for the array is generated from a database table.
I have the following JSON_ENCODED array (created in php):
{"result":
[
{"id":"3","quivername":"Kite Boarding"},
{"id":"4","quivername":"Live and Die in LA"},
{"id":"14","quivername":"Bahamas Planning"},
{"id":"15","quivername":"My Trip to India"},
{"id":"16","quivername":"Snowboarding"}
]
}
UPDATE FOR WEBSERVICE PHP CODE
I run this function as a webservice:
passport($_SESSION['IdUser'])
function passport($userid) {
$result = query("SELECT id, quivername FROM quivers WHERE userid = $userid");
if (!$result['error']) {
print json_encode($result);
} else {
errorJson('Can not get passports');
}
}
function query() {
global $link;
$debug = false;
//get the sql query
$args = func_get_args();
$sql = array_shift($args);
//secure the input
for ($i=0;$i<count($args);$i++) {
$args[$i] = urldecode($args[$i]);
$args[$i] = mysqli_real_escape_string($link, $args[$i]);
}
//build the final query
$sql = vsprintf($sql, $args);
if ($debug) print $sql;
//execute and fetch the results
$result = mysqli_query($link, $sql);
if (mysqli_errno($link)==0 && $result) {
$rows = array();
if ($result!==true)
while ($d = mysqli_fetch_assoc($result)) {
array_push($rows,$d);
}
//return json
return array('result'=>$rows);
} else {
//error
return array('error'=>'Database error');
}
}
I USE THIS CODE IN XCODE TO CONNECT TO THE WEBSERVICE/WEBSITE USING NSNetworking...
-(void)getPassports {
//just call the "passport" command from the web API
[[API sharedInstance] commandWithParams:[NSMutableDictionary dictionaryWithObjectsAndKeys:
#"passport",#"command",
nil]
onCompletion:^(NSDictionary *json)
{
//got passports, now need to populate pickerArray1
if (![json objectForKey:#"error"])
{
//success, parse json data into array for UIPickerview
}
else
//error, no passports
{
// error alert
}
}];
}
I need the following:
1) how can i populate an NSMutable array with the quivername value? I'm trying to get the result to look like this:
pickerArray1 = [[NSMutableArray alloc] initWithObjects:#"Kite Boarding",
#"Live and Die in LA", #"Bahamas Planning", #"My Trip to India", #"Snowboarding",
nil];
I'm assuming I need to run a for loop which would require me to "count" the number of rows in the array first, but I'm not sure. I don't know how to count the number of rows in the json_array or create an NSMutable array.
Thanks in advance...
You should be able to get your NSData from the URL that returns the JSON using [NSData dataWithContentsOfURL:yourURLValue], then pass the data and parse using something similar to what's below based on your JSON:
//INVOKE WEB SERVICE QUERY IN VIEW DID LOAD -- RUNS ASYNC SO WONT BLOCK UI
- (void)viewDidLoad
{
[super viewDidLoad];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData* data = [NSData dataWithContentsOfURL:
#"URLOFYOURWEBSERVICE"]; // <-- may need to cast string to NSURL....
NSMutableArray *quivernames = [self performSelectorOnMainThread:#selector(fetchedData:)
withObject:data waitUntilDone:YES];
});
}
- (NSMutableArray *)fetchedData:(NSData *)responseData {
//parse out the json data
NSError* error;
//ARRAY OR DICT DEPENDING ON YOUR DATA STRUCTURE
NSDictionary* json = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
//ITERATE AND/OR ADD OBJECTS TO YOUR NEW ARRAY
NSMutableArray* JSONResultValues = [json objectForKey:#"yourKey"];
NSMutableArray* resultValues = [[NSMutableArray alloc] init];
for(int x = 0; x< [JSONResultValues count]; x++){
NSDictionary *tempDictionary = [JSONResultValues objectAtIndex:x];
[resultValues addObject:[tempDictionary objectForKey:#"quivername"]];
}
NSLog(#"Results Count: %#", [JSONResultValues count]);
NSLog(#"Results Count: %#", [resultValues count]);
return resultValues;
}
EDIT: For more info check out this explanation for JSON parsing http://www.raywenderlich.com/5492/working-with-json-in-ios-5
You could try this (untested):
//convert this to NSData, not sure how it is getting into obj-c
{"result":[
{"id":"3","quivername":"Kite Boarding"},
{"id":"4","quivername":"Live and Die in LA"},
{"id":"14","quivername":"Bahamas Planning"},
{"id":"15","quivername":"My Trip to India"},
{"id":"16","quivername":"Snowboarding"}
]};
NSMutableArray* myData = [self fetchedData:responseData];
- (NSMutableArray* )fetchedData:(NSData *)responseData {
//parse out the json data
NSError* error;
NSDictionary* json = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error
];
NSMutableArray* quivers = [json objectForKey:#"result"];
return quivers;
}
Wenderlich explains it here...

xcode counting JSON items

I need to Count the number of Items (post) in this JSON response,
2012-06-04 14:09:57.872 horitable[72261:11903] JSON : {
posts = (
{
post = {
eventdate = "2012-03-31";
eventid = 2;
eventimage = "http://hernandoz.local/~hernandoz/kopict/02_31march2012.jpg";
eventinfo = "02 event";
eventname = "xplosion 02";
};
},
{
post = {
eventdate = "2012-07-07";
eventid = 3;
eventimage = "http://hernandoz.local/~hernandoz/kopict/greg_vs_turner.jpg";
eventinfo = "02 event";
eventname = "Xplosion 02";
};
},
{
post = {
eventdate = "2012-04-29";
eventid = 4;
eventimage = "http://hernandoz.local/~hernandoz/kopict/ko_itclub_apr_2012.jpg";
eventinfo = "KO East London Interclub";
eventname = "KO Interclub";
};
}
);
}
I know there are only 3 events (post), this is the code I am using
[[AFNetworkActivityIndicatorManager sharedManager] decrementActivityCount];
NSLog(#"JSON : %#", JSON); //get the JSON response
// 6.1 - Load JSON into internal variable
jsonResponse = JSON;
// 6.2 - Get the number of shows (post)
int shows = 0;
for (NSDictionary* day in jsonResponse) {
shows += [[day objectForKey:#"posts"] count];
NSLog(#"count : %d",shows);
}
I get an error , but I don't understand why .
-[__NSCFString objectForKey:]: unrecognized selector sent to instance 0x76986f0
can someone please help me out . Thanks
try this
NSLog(#"Response members= %#",responseString);
NSArray *array = [(NSDictionary*)[responseString JSONValue] objectForKey:#"posts"];
NSLog(#"Count value= %d",[array count]);
in your case you can do this
[[AFNetworkActivityIndicatorManager sharedManager] decrementActivityCount];
NSLog(#"JSON : %#", JSON); //get the JSON response
// 6.1 - Load JSON into internal variable
jsonResponse = JSON;
// 6.2 - Get the number of shows (post)
int shows = [[jsonResponse objectForKey:#"posts"] count];
NSLog(#"count : %d",shows);
you need to first fragmentize json as
NSDictionary * dict = [JSON JSONValue];
or
NSDictionary * dict = [JSON JSONFragmentValue];
then
for (NSDictionary* day in dict) {
shows += [[day objectForKey:#"posts"] count];
NSLog(#"count : %d",shows);
}
The problem was JSON as an ID, this works
jsonResponse = [(NSDictionary*)JSON objectForKey:#"posts"];
I think you are mistakely put "posts" instead of "post". The key "posts" contain an array of dictionary, and each dictionary has a key "post". What you are doing is you take all dictionaries from array in lines
for (NSDictionary* day in jsonResponse) {
and check for an key "posts" in dictionary. Really, there is no key called "posts" in dictionary. I is "post". And the value for "post" is a NSDictionary not an array. So you cant call count there. The solution for your issue is remove un-necessary APIs inside for loop
[[AFNetworkActivityIndicatorManager sharedManager] decrementActivityCount];
jsonResponse = JSON;
int shows = 0;
for (NSDictionary* day in jsonResponse) {
shows += 1;
}
NSLog(#"count : %d",shows);

SBJSON parser doesn't work on second try

When I call the SBJsonParser the first time, it's working fine but on the second try, the result is always null. The data format is exactly the same as the first.
Here is the code:
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
// the connection finished loading all data, process...
// Store incoming data into a string
NSString *jsonString = [[NSString alloc] initWithData:self.jsonData encoding:NSUTF8StringEncoding];
NSLog(#"%#",jsonString);
if ( jsonString != nil ) {
// Create SBJSON object to parse JSON
SBJsonParser *parser = [[SBJsonParser alloc] init];
NSDictionary *results =
[parser objectWithString:jsonString error:nil];
NSLog(#"results: %#", results);
if ( [[results objectForKey:#"postalCodes"] count] > 0 ) {
// Build an array from the dictionary for easy access to each entry
NSArray *postalCodes_array = [results objectForKey:#"postalCodes"];
int index = 0;
// Loop through each entry in the dictionary...
for (NSDictionary *dict_item in postalCodes_array)
{
if ( index == 0 ) {
appDelegate.curZip = [dict_item objectForKey:#"postalCode"];
NSLog(#"New Zip: %#", appDelegate.curZip);
break;
}
}
postalCodes_array = nil;
parser = nil;
}
results = nil;
}
jsonString = nil;
}
Here are the print out for NSLog lines above:
First try:
2012-05-27 12:19:13.322[16525:707] {"postalCodes": [{"adminName2":"Union","adminCode2":"039","distance":"0","adminCode1":"NJ","postalCode":"07201","countryCode":"US","lng":-74.2099,"placeName":"Elizabeth","lat":40.661369,"adminName1":"New Jersey"},{"distance":"0.28183","adminCode1":"NJ","postalCode":"07216","countryCode":"US","lng":-74.210939,"placeName":"Elizabeth","lat":40.663778,"adminName1":"New Jersey"},{"distance":"0.28183","adminCode1":"NJ","postalCode":"07215","countryCode":"US","lng":-74.210939,"placeName":"Elizabeth","lat":40.663778,"adminName1":"New Jersey"},{"adminName2":"Union","adminCode2":"039","distance":"1.12041","adminCode1":"NJ","postalCode":"07202","countryCode":"US","lng":-74.221544,"placeName":"Elizabeth","lat":40.65652,"adminName1":"New Jersey"},{"adminName2":"Union","adminCode2":"039","distance":"1.72655","adminCode1":"NJ","postalCode":"07206","countryCode":"US","lng":-74.192487,"placeName":"Elizabeth","lat":40.653207,"adminName1":"New Jersey"}]}
Second Try:
2012-05-28 20:16:16.727 [17151:707] {"postalCodes":[{"adminName2":"Kings","adminCode2":"047","distance":"0","adminCode1":"NY","postalCode":"11230","countryCode":"US","lng":-73.956528,"placeName":"Brooklyn","lat":40.618122,"adminName1":"New York"},{"adminName2":"Kings","adminCode2":"047","distance":"1.38292","adminCode1":"NY","postalCode":"11210","countryCode":"US","lng":-73.946682,"placeName":"Brooklyn","lat":40.628064,"adminName1":"New York"},{"adminName2":"Kings","adminCode2":"047","distance":"2.04126","adminCode1":"NY","postalCode":"11229","countryCode":"US","lng":-73.94749,"placeName":"Brooklyn","lat":40.601094,"adminName1":"New York"},{"adminName2":"Kings","adminCode2":"047","distance":"2.45579","adminCode1":"NY","postalCode":"11204","countryCode":"US","lng":-73.985623,"placeName":"Brooklyn","lat":40.617871,"adminName1":"New York"},{"adminName2":"Kings","adminCode2":"047","distance":"2.70498","adminCode1":"NY","postalCode":"11223","countryCode":"US","lng":-73.974291,"placeName":"Brooklyn","lat":40.597874,"adminName1":"New York"}]}{"postalCodes":[{"adminName2":"Richmond","adminCode2":"085","distance":"0","adminCode1":"NY","postalCode":"10306","countryCode":"US","lng":-74.141922,"placeName":"Staten Island","lat":40.564416,"adminName1":"New York"},{"adminName2":"Richmond","adminCode2":"085","distance":"0.41508","adminCode1":"NY","postalCode":"10313","countryCode":"US","lng":-74.146836,"placeName":"Staten Island","lat":40.564393,"adminName1":"New York"},{"adminName2":"Richmond","adminCode2":"085","distance":"1.66907","adminCode1":"NY","postalCode":"10308","countryCode":"US","lng":-74.152649,"placeName":"Staten Island","lat":40.55181,"adminName1":"New York"},{"adminName2":"Richmond","adminCode2":"085","distance":"3.76947","adminCode1":"NY","postalCode":"10312","countryCode":"US","lng":-74.179165,"placeName":"Staten Island","lat":40.545745,"adminName1":"New York"},{"adminName2":"Richmond","adminCode2":"085","distance":"4.41459","adminCode1":"NY","postalCode":"10314","countryCode":"US","lng":-74.147218,"placeName":"Staten Island","lat":40.603915,"adminName1":"New York"}]}
2012-05-28 20:16:16.760 [17151:707] error: Error Domain=org.brautaset.SBJsonParser.ErrorDomain Code=0 "Token 'start of object' not expected after outer-most array or object" UserInfo=0x176560 {NSLocalizedDescription=Token 'start of object' not expected after outer-most array or object}
2012-05-28 20:16:16.761 [17151:707] results: (null)
As you can see, I am doing the init every time. Not sure why it's not working. Any suggestion is appreciated.
Thank you
In your second try the JSON is not valid. That is why it is not being parsed. You can check it here: http://jsonlint.com/
At the end of the JSON string it seems like some junk has been inserted. If you format the string you will find the problem at lines 51-53. Replace the following with a comma:
]
}{
"postalCodes":
On closer inspection it looks like you are only interested in placeName == "Elizabeth". At the very end you have one entry where placeName == "Fanwood". So you probably just want to remove lines 51-62.
As an aside, you could use the error parameter to detect problems with your parser.
NSError *error = nil;
NSDictionary *results = [parser objectWithString:jsonString error:&error];
if (error) {
// we have a problem
}
Why an unexpected array braces closing here?in your second json sring?
"placeName":"Elizabeth","lat":40.653207,"adminName1":"New Jersey"}]}{"postalCodes":{"adminName2":"Union","adminCode2":"039","distance":"3.97758","adminCode1":"NJ","postalCode":"07023","countryCode":"US","lng":-74.386762,"placeName":"Fanwood","lat":40.641856,"adminName1":"New Jersey"}]} .
Its not an issue of init.its an error in the jsonstring that you are getting.