I'm trying to pull data from my web service API and parse the received JSON... I've been though RestKit tutorials, but I couldn't find any information on doing a post request!
Right now I have this code:
-(void) loadPerformers
{
// Create our request mapping
RKObjectMapping* requestMapping = [RKObjectMapping mappingForClass:[JsonOperationModel class]];
[requestMapping addAttributeMappingsFromArray:#[#"RequestType"]];
// Create our data mapping
RKObjectMapping* dataMapping = [RKObjectMapping mappingForClass:[DataModel class] ];
[dataMapping addAttributeMappingsFromArray:#[#"Status"]];
// Create our performer mapping
RKObjectMapping* performerMapping = [RKObjectMapping mappingForClass:[PerformerModel class] ];
[performerMapping addAttributeMappingsFromArray:#[#"IdPerformer", #"Name", #"Rate",
#"IsInWatch", #"Rating", #"PictureUrl", #"LastModifiedDate"]];
// Create our talent mapping
RKObjectMapping* talentMapping = [RKObjectMapping mappingForClass:[DataModel class] ];
[talentMapping addAttributeMappingsFromArray:#[#"Id", #"Value"]];
// Define the relationship mapping with request -> data
[requestMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"data"
toKeyPath:#"data"
withMapping:dataMapping]];
// Define the relationship mapping with data -> performers
[requestMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"Performers"
toKeyPath:#"Performers"
withMapping:performerMapping]];
// Define the relationship mapping with performer -> talent
[requestMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"Talents"
toKeyPath:#"Talents"
withMapping:talentMapping]];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:JsonOperationMapping pathPattern:nil keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
NSURL *URL = [NSURL URLWithString:#"http://10.10.5.106:8089/Mobile/Default.ashx"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
RKObjectRequestOperation *objectRequestOperation = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:#[ responseDescriptor]];
[objectRequestOperation setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
RKLogInfo(#"result: %#", mappingResult.array);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
RKLogError(#"Operation failed with error: %#", error);
}];
[objectRequestOperation start];
}
Which was a sample on an official tutorial of RestKit, modified to my needs according to my json. But how can I adapt this sample to send a JSON string to the URL, and then fetch the server anwser?
I couldn't find any information on that, and each of my server response needs some client information sent through a JSON string before being able to send a response back to the client. (It's isn't only fetching from an URL as my current sample does!)
Thanks for any input on this!
Related
I am trying to make a PUT request using AFNetworking. There is a JSON on a remote location, which I want to update with the request.
I have managed to serialize my object into JSON, and I have put it an NSDictionary object:
NSDictionary *container = [NSDictionary dictionaryWithObject:[self notifications] forKey:#"notifications"];
This way when I print out the container using NSLog, I get precisely the JSON I want to send.
Then I try to use AFNetworking for my request:
AFHTTPClient *putClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:#"http://creativemind.appspot.com"]];
[putClient setParameterEncoding:AFJSONParameterEncoding];
NSMutableURLRequest *putRequest = [putClient requestWithMethod:#"PUT"
path:#"/notifications"
parameters:container];
AFHTTPRequestOperation *putOperation = [[AFHTTPRequestOperation alloc] initWithRequest:putRequest];
[putClient registerHTTPOperationClass:[AFHTTPRequestOperation class]];
[putOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Response: %#", [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding]);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
[putOperation start];
When I try this solution, I get the following error message:
2013-08-10 16:10:40.741 NotificationReader[1132:c07] Error: Error
Domain=AFNetworkingErrorDomain Code=-1011 "Expected status code in
(200-299), got 400" UserInfo=0x75c89e0
{NSLocalizedRecoverySuggestion={"cause":null,"class":"java.lang.NumberFormatException","localizedMessage":"null","message":"null"},
AFNetworkingOperationFailingURLRequestErrorKey=http://creativemind.appspot.com/notifications>,
NSErrorFailingURLKey=http://creativemind.appspot.com/notifications,
NSLocalizedDescription=Expected status code in (200-299), got 400,
AFNetworkingOperationFailingURLResponseErrorKey=}
I think it's important to note that when I try the same request with nil parameters, I get te same error message. I also tried to perform a GET request on the same JSON to check if I can reach it and it works perfectly. I really have no idea what I could do. Any help would be appreciated
I'm trying to run a find by ID request on a REST API. I'm using RestKit 0.20. I have a Location object. It has an id attribute. I want to make a GET request to '/locations/:id' and receive the complete object as JSON.
I have the backend and it's working. I'm now trying to write the iOS client code.
Here's what I have:
RKObjectManager* m = [RKObjectManager sharedManager];
RKObjectMapping* lmap = [RKObjectMapping requestMapping];
[lmap addAttributeMappingsFromArray:#[#"id"]];
RKRequestDescriptor* req = [RKRequestDescriptor requestDescriptorWithMapping:lmap objectClass:[Location class] rootKeyPath:nil];
[m addRequestDescriptor:req];
Location* l = [[Location alloc] init];
l.id = [NSNumber numberWithInt:177];
[m getObject:l path:#"locations/:id" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"LOADED: %#", [mappingResult firstObject]);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"FAILED");
}];
After the code above is ran Restkit does not replace ':id: from the path with the ID attribute set in the Location object.
Do you guys have any ideas what I'm doing wrong?
UPDATE:
I had both request and response descriptors set for the Location class. I had a route added for the find_by_id request but it was a Named Route, not a Class Route. When I used the getObject:path:parameters:success:failure method the router did not fill in the 'id' placeholder (irregardless whether it was named 'id', 'object_id', 'identity' or whatever).
The solution I found is this:
Continue using a Named Route but use the getObjectsAtPathForRouteNamed:object:parameters:success:failure method instead
User a Class Route and continue using the getObject:path:parameters:success:failure method
The problem I was having was that when using a NamedRoute like so:
RKRoute * route = [RKRoute routeWithClass:className pathPattern:path method:RKRequestMethodFromString(method)];
[objectManager.router.routeSet addRoute:route];
and then querying for objects using the getObject:path:parameters:success:failure method did not cause the router to fill out any placeholders in the URL path.
You're using a request descriptor, but your aren't making a 'request' (PUT / POST). When doing a GET you need to use a response descriptor. Also, the mapping you're creating isn't specifying the class (so it's linked against NSDictionary. I'd usually use the response descriptor with a router too. Something like:
RKObjectManager* m = [RKObjectManager sharedManager];
RKObjectMapping* lmap = [RKObjectMapping mappingForClass:[Location class]];
[lmap addAttributeMappingsFromArray:#[#"identity"]];
RKResponseDescriptor* req = [RKResponseDescriptor responseDescriptorWithMapping:lmap pathPattern:#"locations/:identity" keyPath:nil statusCodes:[NSIndexSet indexSetWithIndex:200]];
[m addResponseDescriptor:req];
[m.router.routeSet addRoute:[RKRoute routeWithClass:[Location class] pathPattern:#"locations/:identity" method:RKRequestMethodGET]];
Location* l = [[Location alloc] init];
l.identity = [NSNumber numberWithInt:177];
[m getObject:l path:nil parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"LOADED: %#", [mappingResult array]);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"FAILED");
}];
Problem
I've been trying to post to the server with a multiform request that includes an image attachment. I haven't had trouble getting the image to the server, it is the other information that is not sending correctly.
Details
I'm using object mapping to configure several different attributes when receiving objects from the server:
//Using a custom class to map object I receive to
RKObjectMapping * memoryMapper = [RKObjectMapping mappingForClass:[MemoContent class]];
[memoryMapper mapAttributes:#"created", #"user", #"participants", #"tags", #"text", #"kind", #"video", #"location", nil];
[memoryMapper mapKeyPath:#"_id" toAttribute:#"memoryID"];
//MediaMapper handles the data needed for the Image attachments
RKObjectMapping * mediaMapper = [RKObjectMapping mappingForClass:[MemoMedia class]];
[mediaMapper mapKeyPath:#"processed" toAttribute:#"processed"];
[mediaMapper mapKeyPath:#"original" toAttribute:#"original"];
[mediaMapper mapKeyPath:#"mime" toAttribute:#"mimeType"];
[memoryMapper mapKeyPath:#"media" toRelationship:#"rawMedia" withMapping:mediaMapper];
//
[[RKObjectManager sharedManager].mappingProvider setMapping:memoryMapper forKeyPath:#"memories"];
[RKObjectManager sharedManager].serializationMIMEType = RKMIMETypeFormURLEncoded;
[RKObjectManager sharedManager].acceptMIMEType = RKMIMETypeJSON;
Then, when it comes time to post a photo I update configurations as follows:
RKObjectMapping * memoryMapper = [RKObjectMapping mappingForClass:[MemoContent class]];
[memoryMapper mapAttributes:#"created", #"participants", nil];
[[RKObjectManager sharedManager].mappingProvider setSerializationMapping:memoryMapper forClass:[MemoContent class]];
[[RKObjectManager sharedManager].mappingProvider setMapping:memoryMapper forKeyPath:#"memory"];
Participants are people tagged with the photo. Here is how I'm posting it, similar to this https://github.com/RestKit/RestKit/wiki/Attach-a-File-to-an-RKObjectLoader
[[RKObjectManager sharedManager] postObject:theMemory usingBlock:^(RKObjectLoader * loader){
RKObjectMapping* serializationMapping = [[[RKObjectManager sharedManager] mappingProvider] serializationMappingForClass:[MemoContent class]];
NSLog(#"serializationMapping: %#", serializationMapping);
loader.delegate = APP; //main app delegate posting, updating
NSError* error = nil;
RKObjectSerializer * serializer = [[RKObjectSerializer alloc] initWithObject:theMemory mapping:serializationMapping];
NSDictionary * dictionary = [serializer serializedObject:&error];
RKParams * params = [RKParams paramsWithDictionary:dictionary];
NSData * imageData = UIImagePNGRepresentation(theMemory.photo); //image data
[params setData:imageData MIMEType:#"image/png" forParam:#"attachment"];
loader.params = params;
loader.serializationMIMEType = RKMIMETypeFormURLEncoded;
}];
The server is receiving the image as planned, and actually does receive the 'created' and 'participants' unfortunately it's in a strange format that the server doesn't understand. It includes line breaks and such participants (\n 19843589323 \n created: \n 3-31-2012 00:00 (something like that, I will update when I have access to the logs.
I will give you any extra info you need. Would offer reputation for it if I had enough to do so ;)
In RestKit 0.20.0-pre3, RKObjectManager does have method multipartFormRequestWithObject:method:path:parameters:constructingBodyWithBlock:
An example of this task can be found at the RestKit Github page:
Article *article = [Article new];
UIImage *image = [UIImage imageNamed:#"some_image.png"];
// Serialize the Article attributes then attach a file
NSMutableURLRequest *request = [[RKObjectManager sharedManager] multipartFormRequestWithObject:article method:RKRequestMethodPOST path:nil parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFileData:UIImagePNGRepresentation(image)
name:#"article[image]"
fileName:#"photo.png"
mimeType:#"image/png"];
}];
RKObjectRequestOperation *operation = [[RKObjectManager sharedManager] objectRequestOperationWithRequest:request success:nil failure:nil];
[[RKObjectManager sharedManager] enqueueObjectRequestOperation:operation]; // NOTE: Must be enqueued rather than started
I'm using Restkit with object mapping but I seem to be getting an error saying Encountered errors during mapping: Expected an object mapping for class of type 'Rating', provider returned one for 'Error' - now I have set up mapping and serialisation for both the Rating and Error objects, so I don't understand why it's confused. Can anyone help?
This is the mapping returned from the classes
// Rating class
+ (RKObjectMapping *)getRestKitObjectMapping {
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[Rating class]];
[mapping mapAttributes:#"id", #"mics", #"mdate", #"ipaddress", nil];
[mapping mapKeyPath:#"user_id" toAttribute:#"userID"];
[mapping mapKeyPath:#"fid" toAttribute:#"fid"];
return mapping;
}
// Error class
+ (RKObjectMapping *)getRestKitObjectMapping {
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[Error class]];
[mapping mapKeyPath:#"code" toAttribute:#"code"];
[mapping mapKeyPath:#"message" toAttribute:#"message"];
return mapping;
}
This is the object routing
[[RKObjectManager sharedManager].router routeClass:[Rating class] toResourcePath:#"/mic/:id"];
[[RKObjectManager sharedManager].router routeClass:[Rating class] toResourcePath:#"/mic" forMethod:RKRequestMethodPOST];
[[RKObjectManager sharedManager].mappingProvider setMapping:[Rating getRestKitObjectMapping] forKeyPath:#"ratings"];
[[RKObjectManager sharedManager].mappingProvider setMapping:[Error getRestKitObjectMapping] forKeyPath:#"error"];
[[RKObjectManager sharedManager].mappingProvider setSerializationMapping:[[Rating getRestKitObjectMapping] inverseMapping] forClass:[Rating class]];
[[RKObjectManager sharedManager].mappingProvider setSerializationMapping:[[Error getRestKitObjectMapping] inverseMapping] forClass:[Error class]];
Rating *rating = [[Rating alloc] init];
[[RKObjectManager sharedManager] postObject:rating delegate:self];
These are the delegate methods, the didLoadObjects does get called
- (void)objectLoader:(RKObjectLoader *)objectLoader didFailWithError:(NSError *)error
{
NSLog(#"Object loading failed with error: %# ----- body: %#", error.localizedDescription, objectLoader.response.bodyAsString);
}
- (void)objectLoader:(RKObjectLoader *)objectLoader didLoadObjects:(NSArray *)objects
{
NSLog(#"Objects received: %#", objects);
NSLog(#"Respond body: %#", objectLoader.response.bodyAsString);
}
Here is the JSON response
{"error":{"error":1,"code":342,"message":"You have already rated this one"}}
Also when I look at the $_POST value on my REST PHP backend, it's actually empty which means that it's not receiving the posting object...why is that? My routing seems ok?
Do you have any control over your back-end services?
If so, I'd change how you're sending data back. When you're doing the "postObject" call, the only expected response back is a "Rating". I realize that you set up an "Error" mapping, but since the object you posted is a "Rating" object, it's expecting that back.
What would be more appropriate here would be for the service to change the HTTP status code to 403 (forbidden), or something that is not 200 (OK). Then you can handle the error in the "objectLoader:didFailWithError:" delegate method.
It won't let me attached the params to the request, what am I doing wrong? Params is a Dictionary and endString adds to the sharedClient baseURL.
[[RKClient sharedClient] get:endString usingBlock:^(RKRequest *loader){
loader.params = [RKParams paramsWithDictionary:params];
loader.onDidLoadResponse = ^(RKResponse *response) {
[self parseJSONDictFromResponse:response];
};
loader.onDidFailLoadWithError = ^(NSError *error) {
NSLog(#"error2:%#",error);
};
}];
I get this error:RestKit was asked to retransmit a new body stream for a request. Possible connection error or authentication challenge?
I think you are on the right track. Below is from a working example I found here, about 2/3 the way down the page. Another option for you may be to append the params directly to the URL. I'm not sure if that's feasible for you, but if your parameters are simple then it may be.
- (void)authenticateWithLogin:(NSString *)login password:(NSString *)password onLoad:(RKRequestDidLoadResponseBlock)loadBlock onFail:(RKRequestDidFailLoadWithErrorBlock)failBlock
{
[[RKClient sharedClient] post:#"/login" usingBlock:^(RKRequest *request) {
request.params = [NSDictionary dictionaryWithKeysAndObjects:
#"employee[email]", login,
#"employee[password]", password,
nil];
request.onDidLoadResponse = ^(RKResponse *response) {
id parsedResponse = [response parsedBody:NULL];
NSString *token = [parsedResponse valueForKey:#"authentication_token"];
//NSLog(#"response: [%#] %#", [parsedResponse class], parsedResponse);
if (token.length > 0) {
NSLog(#"response status: %d, token: %#", response.statusCode, token);
[[RKClient sharedClient] setValue:token forHTTPHeaderField:#"X-Rabatme-Auth-Token"];
if (loadBlock) loadBlock(response);
}
[self fireErrorBlock:failBlock onErrorInResponse:response];
};
request.onDidFailLoadWithError = failBlock;
}];
}
You should also take a look at this SO question: RestKit GET query parameters.