Here's the scenario; I have a JSON response from a API. I obtain a response from the API as follows:
[request setURL:[NSURL URLWithString:[NSString stringWithFormat:#"http://apitest.maranatha.org/api/SiteGroupStagings?countryId=%i",[country getCountryID]]]];
[request setHTTPMethod:#"GET"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[request setValue:token forHTTPHeaderField:#"Token"];
NSData *returnData = [NSURLConnection sendSynchronousRequest: request returningResponse: nil error: nil];
if (returnData) {
NSDictionary* jsonResponse = [NSJSONSerialization
JSONObjectWithData:returnData options:NSJSONReadingMutableContainers
error:&error];
}
The API will return a JSON array of objects when the API call is correct, the return looks like this:
[
{},
{},
...
]
If there is any problem processing the request on the server side (other than lack of internet connectivity on the client side), the response from the API is a as follows:
{
"message": "In lorem ipsum"
}
I want to check if that key/value pair is present, to be able to alert the user, and not attempt to process the response which would cause an exception to occur.
I've tried the following approach, but it doesn't seem to work, it seems it can always find a message key, even when the JSON response is an array of objects.
if ([jsonResponse valueForKey:#"message"]) {
NSLog(#"%#", [jsonResponse valueForKey:#"message"]);
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error"
message:[jsonResponse valueForKey:#"message"]
delegate:self
cancelButtonTitle:#"Ok" otherButtonTitles:nil];
[alert show];
}
else {
//consume the JSON response
}
How can I successfully check if the response from the API contains the message key/value pair?
It sounds like your server API returns JSON entities with a key "message" for successful requests as well, correct? If that's the case, maybe try this:
if (jsonResponse[#"message"] &&
[jsonResponse[#"message"] isKindOfClass:[NSString class]] &&
[jsonResponse[#"message"] isEqualToString:#"In lorem ipsum"])
{
// Alert
}
This should give you better (but not necessarily complete) protection against runtime variance in the content of the JSON entity as a whole.
Thanks to #fullofsquirrels I got an idea of how to solve the problem.
If a JSON is an JSON Array, [NSJSONSerialization] will make it a NSArray, so the easiest way was to check if my response was an array, or if it wasn't. Here's my solution.
if (![jsonResponse isKindOfClass:[NSArray class]]) {
NSLog(#"%#", [jsonResponse valueForKey:#"message"]);
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error"
message:[jsonResponse valueForKey:#"message"]
delegate:self
cancelButtonTitle:#"Ok" otherButtonTitles:nil];
[alert show];
}
else {
//consume API
}
Related
Here's what I'm doing: I'm processing a login that requires manipulating the data afterwards then fires off a new view. I want to do an async request since the server isn't always immediately responsive (and I don't want a crash because I held up the main thread with a synchronous connection)
This is what it looks like now:
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *dataReturn, NSData *data, NSError *error)
{
//blah blah working code
//once data is received, it's processed and I want to call a new view
viewController.hidesBottomBarWhenPushed = YES;
viewController.name = returnUserData[0];
viewController.userID = returnUserData[1];
viewController.role = returnUserData[2];
viewController.sID = returnUserData[3];
[self.navigationController pushViewController:viewController animated:YES];
NSLog(#"Pushed new view controller.");//*/
}];//end async request
Now my problem is that it's not actually visually pushing the view. I can see my NSLog responses are working correctly (the new view immediately responds with "Hello, [name]"), but visually nothing is showing up - This is a problem.
That's fine though, I decided to instead separate the code and try to run the view transition on the main thread
This is an adaptation of I've seen posted online:
NSLog(#"init NSURL response");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(#"Begin async request");
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *dataReturn, NSData *data, NSError *error)
{
NSLog(#"inside async");
if (error)
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error" message:#"There was an error communicating with the server. Please try again." delegate:nil cancelButtonTitle:#"Dismiss" otherButtonTitles:nil];
[alert show];
[alert release];
checkField.text = #"";
}
else
{
//NSLog(#"raw: %# - filtered: %#",dataReturn, (NSString *)dataReturn);
NSDictionary *response = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
rawJSONData = [response objectForKey:#"d"];
NSLog(#"Set jsondata");
}
NSLog(#"ended async");
}];//end async request
NSLog(#"Beginning main thread work");
dispatch_sync(dispatch_get_main_queue(), ^{
//not important for the example
So what it's doing is giving me this:
2014-03-28 21:53:37.059 myApp[652:60b] init NSURL response
2014-03-28 21:53:37.059 myApp[652:3507] Begin async request
2014-03-28 21:53:37.059 myApp[652:3507] Beginning main thread work
It's skipping over the embedded async request entirely.
So what I'm left with is two off-the-main-thread-solutions that aren't doing what I want:
I want to run NSURLConnection off the main thread, I'm getting back JSON data I need to parse, and I want to wait until I get & parse that data before transitioning views.
Is that I'm trying to do possible? Or are my eyes glazed over and I'm just not seeing something I should be?
In your first example, you can probably fix it by either using [NSOperationQueue mainQueue] for the queue rather than what I presume is some background queue. Or dispatch your push to the main queue. UI updates always should be done on the main queue.
A couple of thoughts regarding your second example:
You don't have to dispatch your sendAsynchronousRequest request to a background queue, because it will do it asynchronously already. Wrapping it in a dispatch_async is redundant.
If you want to initiate some transition to the next view once you've successfully parsed the JSON, then put that code to transition to the next view inside the completionHandler block, right where you parse the JSON, not after the sendAsynchronousRequest.
You can use your queue object if you want, but then make sure you dispatch your UIAlertView and your "transition to view controller" code back to the main queue (because all UI updates should take place on the main queue). Frankly, it's easier to just specify the mainQueue as the queue for the completionHandler (because nothing you have there is so time consuming as to warrant the use of a background queue).
Merely as a matter of convention, I'd suggest leaving the NSURLResponse object called response, and rename that other variable.
You might want to include more error checking (e.g. was the JSON successfully parsed). There are many server errors that would not result in the NSError object of the completionHandler to be set (e.g. that's just for basic internet connectivity problems, not random server problems).
Thus, this yields:
NSLog(#"Issuing asynchronous request");
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
NSLog(#"Received response");
if (error)
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error" message:#"There was an error communicating with the server. Please try again." delegate:nil cancelButtonTitle:#"Dismiss" otherButtonTitles:nil];
[alert show];
[alert release];
checkField.text = #"";
}
else
{
//NSLog(#"raw: %# - filtered: %#",dataReturn, (NSString *)dataReturn);
NSError *parseError = nil;
NSDictionary *responseObject = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:&parseError];
if (responseObject)
{
rawJSONData = [responseObject objectForKey:#"d"];
NSLog(#"Set jsondata");
// initiate the transition to the new view controller here
}
else
{
NSLog(#"JSON parse failed: %#", parseError);
NSLog(#"string from server: %#", [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]);
}
}
NSLog(#"ended async");
}];//end async request
Clearly, you should be checking the response and that the JSON parsing
I am making a call to twitters API to load some tweets for a specific section of my app.
A small chunk of users are reporting a crash when loading the tweets view, while the rest have no problem at all.
I have submitted the code to Apple Tech Support and they responded letting me know that NSJSONSerialization can sometimes return a NSArray or NSDictionary.
Obviously it will throw an error is objectAtIndex: is called on an NSDictionary object, which I believe is the culprit for all of my users.
The partial solution is to detect if it is an Array or NSDictionary.
Here is where I am at now:
id feedData = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&jsonError];
if ([feedData isKindOfClass:[NSArray class]]) {
//Is array
} else if ([feedData isKindOfClass:[NSDictionary class]]) {
//is dictionary
}
I basically need an NSArray every single time. So in the is array block, I basically just use the feedData, but in NSDictionary, how can I convert it to an NSArray that will match the structure I need.
Honestly the biggest issue is that I cannot see what the NSDictionary structure looks like because none of my testing devices or simulator return the NSDictionary data, they all return an NSArray.
Here is what the entire getUserFeed method that sends the request to twitter looks like:
// Get the twitter feed
NSURL *requestURL = [NSURL URLWithString:TW_API_TIMELINE];
// Set up proper parameters
NSMutableDictionary *timelineParameters = [[NSMutableDictionary alloc] init];
[timelineParameters setObject:kNumTweets forKey:#"count"];
[timelineParameters setObject:#"1" forKey:#"include_entities"];
// Create the Social Request
SLRequest *postRequest = [SLRequest requestForServiceType:SLServiceTypeTwitter requestMethod:SLRequestMethodGET URL:requestURL parameters:timelineParameters];
postRequest.account = self.delegate.userAccount;
// Perform the request
[postRequest performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
// Check if we reached the reate limit
if ([urlResponse statusCode] == 429) {
// Rate limit reached
// Display an alert letting the user know we have hit the rate limit
UIAlertView *twitterAlert = [[UIAlertView alloc] initWithTitle:kRateLimitTitle
message:kRateLimitMessage
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[twitterAlert show];
// Stop animating the pull to refresh if it is animating
[self.feedTableView.pullToRefreshView stopAnimating];
return;
}
// Check if there was an error
if (error) {
NSLog(#"Error: %#", error.localizedDescription);
// Stop animating the pull to refresh if it is animating
[self.feedTableView.pullToRefreshView stopAnimating];
return;
}
// Check if there is some response data
if (responseData) {
NSError *jsonError = nil;
id feedData = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingMutableContainers error:&jsonError];
if ([feedData isKindOfClass:[NSArray class]]) {
//Is array
NSLog(#"It's an Array");
} else if ([feedData isKindOfClass:[NSDictionary class]]) {
//Is dictionary
NSLog(#"It's a Dictionary");
} else {
//is something else
}
if (!jsonError) {
[self gatherTweetsFromArray:feedData];
} else {
// Stop animating the pull to refresh if it is animating
[self.feedTableView.pullToRefreshView stopAnimating];
// Alert the user with the error
UIAlertView *twitterAlert = [[UIAlertView alloc] initWithTitle:kErrorTitle
message:kErrorMessage
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[twitterAlert show];
}
} else {
// Stop animating the pull to refresh if it is animating
[self.feedTableView.pullToRefreshView stopAnimating];
// Alert the user with the error
UIAlertView *twitterAlert = [[UIAlertView alloc] initWithTitle:kErrorTitle
message:kErrorMessage
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[twitterAlert show];
}
});
}];
This is a MAJOR bug and I need to squash it, so any ideas or information will be greatly appreciated! Thank you!
I want to fetch some data from some url and print result in nslog.
I am passing URl and want to fetch result in log only.
I have used this code :
-(void)GETJSONDATA
{
NSString*lu=#"tmp";
NSString *requestString = [[NSString alloc]init];
// [[NSUserDefaults standardUserDefaults] setValue:nil forKey:#"WRONGANSWER"];
NSLog(#"request string:%#",requestString);
NSData *requestData = [NSData dataWithBytes: [requestString UTF8String] length: [requestString length]];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:
[NSURL URLWithString: #"http://assessments.tlisc.org.au/webservices/questions/getbytaskpart.php?jsoncallback=?&token=1726204214321678|xTAieBBJoDaWmBsG1stxfq4zLO4&taskpartid=1"]];
NSString *postLength = [NSString stringWithFormat:#"%d", [requestData length]];
[request setHTTPMethod: #"POST"];
[request setValue:postLength forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:requestData];
NSError *respError = nil;
NSData *returnData = [NSURLConnection sendSynchronousRequest: request returningResponse: nil error: &respError ];
if (respError)
{
// NSString *msg = [NSString stringWithFormat:#"Connection failed! Error - %# %#",
// [respError localizedDescription],
// [[respError userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]];
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Test" message:#"check your network connection" delegate:self cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alertView show];
}
else
{
NSString *responseString = [[NSString alloc] initWithData:returnData encoding: NSUTF8StringEncoding];
NSLog(#"Resp : %#",responseString);
NSDictionary *results = [responseString JSONValue];
NSLog(#"results=%#",results);
}
}
It is showing me this error :
-JSONValue failed. Error trace is: (
"Error Domain=org.brautaset.JSON.ErrorDomain Code=11 \"Unexpected end of string\" UserInfo=0x6a42be0 {NSLocalizedDescription=Unexpected end of string}"
Can anybody point me error?
In response string , it is showing null value.
I don't know the actual error.
it is working fine in browser but when I parse it it is showing this error....Is there anyway through which I can modify the url and get result ...i have checked my code with different url.and it is working proper..
I think I was using the same library as you're using here, and for some unexplained reason it just failed on a particular google api, sometimes getting results but usually failing. There appeared to be nothing wrong with the code.
In the end I went with the inbuilt NSJSONSerialization ( http://developer.apple.com/library/ios/#documentation/Foundation/Reference/NSJSONSerialization_Class/Reference/Reference.html ) object and it hasn't failed since. I wasn't even aware there was JSON support built in (from iOS5 apparently).
I ran the two calls and processed them side by side, the external library continually failed. The internal methods worked fine.
Since I'm a beginner and the docs (above) don't really help me a lot, I used this tutorial http://www.raywenderlich.com/5492/working-with-json-in-ios-5 to get my head around it
As the error told you: the incoming JSON is not well-formed. Making a simple GET request to the URL you are using, I'm getting a JSON that is malformed, i.e. it is not valid JSON. I guess posting data to this URL returns the same format with actual data in it (not null).
Making use of a simple JSON Validator, I'm getting this
Parse error on line 5: ... "Description": "\n\tQuestion Numbe
-----------------------^ Expecting 'STRING', 'NUMBER', 'NULL', 'TRUE', 'FALSE', '{', '['
Check your url in a browser. See the result. Your result's Structure should be like the same as JSON. if there is any other extra word in start of the json means. you should check your php code.
You have got HTML tags within your strings. Paragraph marks like
<p> and </p>
And these are not properly escaped. However, I do not know by hard wether the less than sign and greater than sign need to be escaped in JSON but apparently they are creating the issue.
Using google I find statements for both. However, some site suggests escaping < with \u003c and > with \u003e. that should do the trick.
I tried to validate the output from
http://assessments.tlisc.org.au/webservices/questions/getbytaskpart.php?jsoncallback=?&token=1726204214321678|xTAieBBJoDaWmBsG1stxfq4zLO4&taskpartid=1
It seems to be valid though if the <p> and </p> ist not interpreted by the bowser. (I was lookint to the "page source").
However, when escaped the string is still valid and encodes to the same result.
If this is not your issue, then please provide the output of your
NSLog(#"Resp : %#",responseString);
Developing an app for iOS, I need to know how to have instanced and available an object created when user authenticates.
I am using OAuth2 method properly implementing gtm-oauth2 framework. The user entries, sees the login form displayed in a web view and correctly authenticates. In that moment, as detailed in the documentation, I go like this:
if (error != nil){
// Do whatever to control the error
}
else
{
// Authentication succeeded
// Assign the access token to the instance property for later use
self.accessToken = myAuth.accessToken;
[myAuth setShouldAuthorizeAllRequests:YES];
[self setAuth:myAuth];
// Display the access token to the user
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Authorization Succeeded"
message:[NSString stringWithFormat:#"Access Token: %#", myAuth.accessToken]
delegate:self
cancelButtonTitle:#"Dismiss"
otherButtonTitles:nil];
[alertView show];
}
Later, in the same controller, I use the self.auth object like this to access my API once the user has authenticated:
[request setURL:getCartsURL];
[request setValue:self.accessToken forHTTPHeaderField:#"Authorization"];
[self.auth authorizeRequest:request
completionHandler:^(NSError *error) {
NSString *output = nil;
if (error) {
output = [error description];
} else {
// Synchronous fetches like this are a really bad idea in Cocoa applications
//
// For a very easy async alternative, we could use GTMHTTPFetcher
NSURLResponse *response = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
if (data) {
// API fetch succeeded
output = [[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding];
SBJsonParser *jsonParser = [SBJsonParser new];
// Parse the JSON into an Object
id parsed = [jsonParser objectWithString:output];
NSArray *arrayResponse = [[NSArray alloc] initWithArray:parsed];
} else {
// fetch failed
output = [error description];
}
}
}];
So far, I have been using a local instance of self.auth object, what happens to be insufficient if I want to have that object globally accessed from any point of the whole app. Ok for the init view controller, but not for the whole app.
I think I can somehow access this first view controller to get the object anytime I want it. But I guess we have better methods to have it globally instanced and accessible from any point of the app.
Can you please help me with this?
Thanks a lot.
You should use a Singleton. Here is a nice article on how to set one up.
You could change the [self setAuth:myAuth]; of that ViewController to set an object on the AppDelegate. Create it there and set it, then you'll be able to access it from anywhere.
[[UIApplication sharedApplication] delegate] will give you a pointer to your app delegate, the one that was automatically created when you made the project.
I am trying to read a plist from a server. I know for a fact that the plist is valid since when I go to the URL i get the plist downloaded and the plist editor shows me my array properly. Now when I use the following code I get now array populated. I see the data is in there. In debugger i see that plistData has value however the array petListArray has no data at all. What is wrong?
- (void)getPetListRequestFinished:(ASIHTTPRequest *)request
{
NSData *plistData = [request responseData];
NSError *error = nil;
NSPropertyListFormat format = NSPropertyListBinaryFormat_v1_0;
NSArray *petListArray = (NSArray *)[NSPropertyListSerialization propertyListWithData:plistData
options:(NSPropertyListReadOptions)NSPropertyListImmutable format:&format error:(NSError **)error];
if(error){
UIAlertView * alert = [[UIAlertView alloc] initWithTitle:#"GetPetListError" message:[NSString stringWithFormat:#"getPettListRequestFinished_new deserialization error: error = %#", error]
delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
[alert release];
return;
}
----snip-------
You are passing the wrong type of argument for the error: slot of - NSPropertyListSerialization propertyListWithData:options:format:error:. Try:
[NSPropertyListSerialization propertyListWithData: plistData
options: (NSPropertyListReadOptions) NSPropertyListImmutable
format: &format
error: &error]
(edited after checking docs)