I currently have the code below to decode some received JSON data. However, since Feedly doesn't give any indexes back I don't know how to properly decode the JSON data. This is an example of JSON I receive back:
[
{
"id": "user/c805fcbf-3acf-4302-a97e-d82f9d7c897f/category/tech",
"label": "tech"
},
{
"id": "user/c805fcbf-3acf-4302-a97e-d82f9d7c897f/category/design",
"label": "design"
}
]
And if I let the code below run, the NSLog outputs it perfectly. But how do I fetch the label variables from res?
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSLog(#"connectionDidFinishLoading");
NSLog(#"Succeeded! Received %d bytes of notifications",[self.responseData length]);
// convert to JSON
NSError *myError = nil;
NSDictionary *res = [NSJSONSerialization JSONObjectWithData:self.responseData options:NSJSONReadingMutableLeaves error:&myError];
NSLog(#"res value contains: %#", res);
}
If short to get label values use [res valueForKeyPath:#"label"];
But to make it work in right way you need to change type of res to NSArray because provided JSON contains array of dictionary.
Then you can traverse through with for-in.
NSError *myError = nil;
NSArray *res = [NSJSONSerialization JSONObjectWithData:self.responseData options:NSJSONReadingMutableLeaves error:&myError];
for (NSDictionary *d in res) {
NSLog(#"label %#",[d objectForKey:#"label"]);
}
NSLog(#"res value contains: %#", res);
Related
Access paging in iOS Code from JSON previous and next link
In JSON object retrieved from graph api, due to loads of information the information is paged, when I try to access the previous and next link it gives OAuth error.
Can anyone help in how to access the link through objective C in iOS app.
I want to know two things - how to retrive 'N' items using --> limit=N
and how to open a FBSDKgraphrequest and using the paged link link (containing the paging information)
paging = {
next = "https://graph.facebook.com/v2.3/897401690298128/inbox?access_token=CAAEsxMhiSe4BACWnj27BT6ZBvj2BAxNZCtCNQyCKQORXyylXXkQy3DLSF75UGSz2FydAkQx6Pj49MOS0Q3SGiU1vkQ1iUEs2fQvvlwW3Wc04DEnXZB4CZCza7tOJfyncIPrkFrudQCeRhWUUREqMpCI8Dnm6Ozc6xmwOlT1uN2ZCgQ91llcVC1kV04fiZCqO6H6edFe2YZAUZBy86pw1p4SWCUvgMshzkvZBGgpG8UWG50ZCShdeQPUc86fsuQGOcAno0ZD&limit=25&until=1428241306&__paging_token=enc_AdC9127ZCBVnZACHUlMZBTC39ZC8bSP4ZA8uwQZBdy8xhsZAyKAcxxNdqn48Er3CrVM4DkJPATHhOYBVRm8FuCvYZBU8KSpZA";
previous = "https://graph.facebook.com/v2.3/897401690298128/inbox?access_token=CAAEsxMhiSe4BACWnj27BT6ZBvj2BAxNZCtCNQyCKQORXyylXXkQy3DLSF75UGSz2FydAkQx6Pj49MOS0Q3SGiU1vkQ1iUEs2fQvvlwW3Wc04DEnXZB4CZCza7tOJfyncIPrkFrudQCeRhWUUREqMpCI8Dnm6Ozc6xmwOlT1uN2ZCgQ91llcVC1kV04fiZCqO6H6edFe2YZAUZBy86pw1p4SWCUvgMshzkvZBGgpG8UWG50ZCShdeQPUc86fsuQGOcAno0ZD&limit=25&since=1432299972&__paging_token=enc_AdDp9ZCK2ZBP40AgTi4TCzaB0QFT1Cy7s1R7HLLDDaT7nbnLYDZB4LZBjiONOqG5QR9Q22KY1oU1LzNOwS5uNZBG7uLF4&__previous=1";
};
You can still use the SDK from Facebook, you only have to give the "after" field as a parameter. Then call a recursive function.
A solution I have just implemented in Swift:
func getFBTaggableFriends(nextCursor : String?, failureHandler: (error: NSError) -> Void) {
var qry : String = "me/taggable_friends"
var parameters = Dictionary<String, String>() as? Dictionary
if nextCursor == nil {
parameters = nil
} else {
parameters!["after"] = nextCursor
}
// Facebook: get taggable friends with pictures
var request = FBSDKGraphRequest(graphPath: qry, parameters: parameters)
request.startWithCompletionHandler { (connection : FBSDKGraphRequestConnection!, result : AnyObject!, error : NSError!) -> Void in
if ((error) != nil)
{
// Process error
println("Error: \(error)")
}
else
{
//println("fetched user: \(result)")
var resultdict = result as! NSDictionary
var data : NSArray = resultdict.objectForKey("data") as! NSArray
for i in 0..<data.count {
let valueDict : NSDictionary = data[i] as! NSDictionary
let id = valueDict.objectForKey("id") as! String
let name = valueDict.objectForKey("name") as! String
let pictureDict = valueDict.objectForKey("picture") as! NSDictionary
let pictureData = pictureDict.objectForKey("data") as! NSDictionary
let pictureURL = pictureData.objectForKey("url") as! String
println("Name: \(name)")
//println("ID: \(id)")
//println("URL: \(pictureURL)")
}
if let after = ((resultdict.objectForKey("paging") as? NSDictionary)?.objectForKey("cursors") as? NSDictionary)?.objectForKey("after") as? String {
self.getFBTaggableFriends(after, failureHandler: {(error) in
println("error")})
} else {
println("Can't read next!!!")
}
}
}
}
You will then call this function with:
getFBTaggableFriends(nil, failureHandler: {(error)
in println(error)});
Page information has the current access token, and also depending up different limits restriction paging values will always change , so; its best to use the url and fetch the result.
Because the result of next will also have pointer to both previous and next , best method to parse the result obtained is by calling it through a recursive function and passing the paging next values outside and inside the calling function to traverse all pages.
Don't forget to put a restriction where you want to stop traversing other nodes.
eg:
NSURL *url = [NSURL URLWithString:PagingNext];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *errorConnection)
{
if(errorConnection == nil)
{
NSError *error;
id result = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
if (result == nil)
{
NSLog(#"Error parsing JSON:\n%#",error.userInfo);
return;
}
//if no error then extract paging next and do what you want ->
/*
result = {
data[20],
paging={
previous= "URL_of_previous"
next = "URL_of_next"
}
}
*/
if ([result isKindOfClass:[NSDictionary class]])
{
NSArray * data = [result objectForKey:#"data"];
NSDictionary *paging =[result objectForKey:#"paging"];
NSString * resultPagingNext = [paging objectForKey:#"next"];
[self call_method_recursively_with_parameter_of_next_page:data pagingNext:resultPagingNext];
}
}
else
{
NSLog(#"Connection Error:\n%#", errorConnection.userInfo);
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:#"Network Issue"
message:#"Check if you are connected to Internet" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action)
{
NSLog(#"User has pressed OK."); }];
[alertController addAction:okAction];
[self presentViewController:alertController animated:YES completion:nil];
}
}];
is it possible to pass custom data in al_applink_data using Facebook applinks?
I can retrieve this JSON example but I cannot see a place where to append my custom data to it. If this is not possible than my only solution is to parse obtained URL but this doesn't seem much bulletproof.
{
"target_url": "https://www.example.com/abc.html",
"extras": {
"fb_app_id": [YOUR_FACEBOOK_APP_ID],
"fb_access_token": "[ACCESS_TOKEN']",
"fb_expires_in": "3600"
},
"referer_app_link": {
"url": "[FACEBOOK_APP_BACK_LINK]",
"app_name": "Facebook"
}
}
Parsing Data
My solution by creating custom data for target_url.
NSDictionary *dictionary = #{ #"target_url" : #"YOUR_VALUE"};
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dictionary options:0 error:nil];
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
Then, append with your Facebook app link ID with al_applink_data key in FB Graph Object dictionary.
[NSString stringWithFormat:#"https://fb.me/FB_LINK_ID?al_applink_data=%#", jsonString]
That's it.!!
Retrieving Callback URL
if([[call appLinkData] targetURL] != nil)
{
NSURL *targetUrl = [[call appLinkData] targetURL];
//Actual URL
NSString *urlString = [[targetUrl absoluteString] stringByRemovingPercentEncoding];
URLParser *parser = [[URLParser alloc] initWithURLString:urlString];
//Fetching value for 'al_applink_data'
NSString *appLinkData = [parser valueForVariable:#"al_applink_data"];
NSData *objectData = [appLinkData dataUsingEncoding:NSUTF8StringEncoding];
//Dictionary with 'target_key' key and its value.
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:objectData options:NSJSONReadingMutableContainers error:nil];
NSLog(#"%#", json);
}
Reference for URL parsing : URLParser
Thanks.
The "extras" map was designed to carry arbitrary metadata. What type of custom data do you need? Passing of custom data through the "extras" blob requires the caller to know something about your app (so they can actually add the data).
I have a mutable array stories that I'm trying to add a copy of JSON response to but it yields O stories and nothing in the array. What am I doing wrong? The JSON object is properly formatted and I can display each component individually but they will not add to the array.
NSString *str=#"http://www.foo.com/some.php";
NSURL *url=[NSURL URLWithString:str];
NSData *data=[NSData dataWithContentsOfURL:url];
NSError *error=nil;
NSMutableDictionary *response=[NSJSONSerialization JSONObjectWithData:data options:
NSJSONReadingMutableContainers error:&error];
NSLog(#"Your JSON Object: %#", response);
NSLog(#"Location: %#", response[#"location"]);
NSLog(#"Service ID: %#", response[#"serviceID"]);
NSLog(#"Problem: %#", response[#"problem"]);
[stories addObject:[response copy] ];
NSLog(#"Workorders: %d", [stories count]);
NSLog(#"WO Defined: %#",stories);
There you go...try this
NSMutableArray *stories = [NSMutableArray array];
I have an API that requires I post a complex JSON object. The API saves and responds with a primitive ID (or an error object). I am not able to map the primitive ID. Any ideas? The example below, for simplicity's sake, is not doing the POST object mapping, but some of my API calls will require that as well.
I saw suggestions to utilize RestKit to build the request, and pass it to AFNetworking, but this will not parse a possible error return object.
RKObjectMapping* map = [RKObjectMapping mappingForClass:[MyPrimitiveResponse class]];
[map addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:nil toKeyPath:#"success"]];
RKResponseDescriptor *errDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:[MyErrorResponse objectMap] method:RKRequestMethodGET pathPattern:nil keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassServerError)];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:map method:RKRequestMethodGET pathPattern:nil keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
NSURL *URL = [NSURL URLWithString:apiUrl];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
RKObjectRequestOperation *objectRequestOperation = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:#[ errDescriptor, responseDescriptor ]];
[objectRequestOperation setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
RKLogInfo(#"Load collection of Articles: %#", mappingResult.array);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
RKLogError(#"Operation failed with error: %#", error);
}];
[objectRequestOperation start];
I get the following error in the debugger:
2013-10-29 10:23:15.196 TestParser[6988:70b] E app:MyHomeViewController.m:54 Operation failed with error: Error Domain=org.restkit.RestKit.ErrorDomain Code=-1017 "Loaded an unprocessable response (200) with content type 'application/json'" UserInfo=0x8daca20 {NSErrorFailingURLKey=....., NSUnderlyingError=0x8db4cd0 "The operation couldn’t be completed. (Cocoa error 3840.)", NSLocalizedDescription=Loaded an unprocessable response (200) with content type 'application/json'}
UPDATE:
This is my final bits of code to handle this situation. It may prove useful to others...
// Manually Map Request to JSON & send to server
NSDictionary *parameters = [RKObjectParameterization parametersWithObject:payload requestDescriptor:[payload.class requestDescriptor] error:&error];
NSMutableURLRequest* request = [self.apiManager requestWithObject:nil method:RKRequestMethodPOST path:url parameters:parameters];
RKHTTPRequestOperation *requestOperation = [[RKHTTPRequestOperation alloc] initWithRequest:request];
[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
unichar firstChar = [operation.responseString characterAtIndex:0];
NSData *newData;
if (firstChar != '{' && firstChar != '[') {
// Force into JSON array so it can be parsed normally
newData = [[[#"[" stringByAppendingString:operation.responseString] stringByAppendingString:#"]"] dataUsingEncoding:NSUTF8StringEncoding];
} else {
newData = operation.responseData;
}
// Parse JSON response into native object, whether primitive NSNumber (integer or boolean) response or a full fledged JSON error object.
RKResponseDescriptor *errDescriptor = [MyErrorResponse responseDescriptor];
RKObjectResponseMapperOperation* mapper = [[RKObjectResponseMapperOperation alloc] initWithRequest:request response:operation.response data:newData responseDescriptors:#[errDescriptor, [payload.class responseDescriptor]]];
[mapper setDidFinishMappingBlock:^(RKMappingResult *mappingResult, NSError *error) {
if (mappingResult) { //Success
RKLogInfo(#"Load response: %#", mappingResult.firstObject);
} else {
RKLogError(#"Operation failed with error: %#", error);
}
}];
[mapper start];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
RKLogError(#"Operation failed with error: %#", error);
}];
[requestOperation start];
I saw suggestions to utilize RestKit to build the request, and pass it to AFNetworking
Yes, do that.
but this will not parse a possible error return object
True, but you can use a mapping operation in RestKit to process that. Or, if the error response is relatively simple just use NSJSONSerialization (or similar) to convert the response.
The 'primitive ID' should be mappable with a nil-keypath mapping. But you may still have issues in mapping an error response if it isn't being mapped back into the source object used in the POST request.
I am trying to parse JSON without keys.
It looks like this one:
{
"somestring": [
"otherstring1",
float],
"somestring2": [
"somestring3",
float],
"full":integer
}
How I am suppose to parse the first value for each object?
So, when you parse that, you will have a NSDictionary with for which the first two keys have a value which is an NSArray:
NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if (error)
NSLog(#"JSONObjectWithData error: %#", error);
NSArray *array = dictionary[#"22398f2"];
NSString *firstArrayItem = array[0]; // #"CBW32"
NSString *secondArrayItem = array[1]; // #50.1083
Or, if you want all of the first items, you could do something like:
NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if (error)
NSLog(#"JSONObjectWithData error: %#", error);
[dictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
NSArray *array = obj;
if ([obj isKindOfClass:[NSArray class]])
NSLog(#"first item = %#", array[0]);
else
NSLog(#"The value associated with key '%#' is not array", key);
}];