Restkit object mapping encountered errors during mapping - iphone

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.

Related

RestKit send post request, parse the result?

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!

Restkit will not insert object attributes to path pattern for request URL path

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");
}];

RestKit mapping error when sending image with RKObjectMapping

Here is the object RestKit mapping:-
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[ImageModel class]];
[mapping mapKeyPath:#"Description" toAttribute:#"description"];
[mapping mapKeyPath:#"Photo1" toAttribute:#"photo1"];
[objectManager.mappingProvider addObjectMapping:mapping];
RKObjectMapping* serializeMapping = [mapping inverseMapping];
[objectManager.mappingProvider setSerializationMapping:serializeMapping forClass:[ImageModel class]];
[objectManager.router routeClass:[ImageModel class] toResourcePath:#"/image" forMethod:RKRequestMethodPOST];
[objectManager.mappingProvider setObjectMapping:mapping forResourcePathPattern:#"/image"];
And here is the ImageModel object:-
#property (strong,nonatomic) NSString *description;
#property (strong,nonatomic) UIImage *photo1;
Here are three different ways that i have tried but all with errors:-
Method1.
Simply use the following code to post object.
obj.description = self.descriptionText.text;
obj.photo1 = selectedImage;
[[RKObjectManager sharedManager] postObject:obj delegate:self];
Result:-
The object is posted to the server and the server script returns the same object back. The response is received at the client side but the following mapping related exception occurred.
2012-10-26 20:16:39.015 APP[4548:7407] W restkit.object_mapping:RKObjectMappingOperation.m:244 Failed transformation of value at keyPath 'Photo1'. No strategy for transforming from '__NSCFString' to 'UIImage'
The log does show that the returned object's Photo1 field does have photo data, but what's missed from the mapping is not understandable. Any idea from RestKit gurus will be very helpful!
Method 2 - Call sendObject:toResourcePath:usingBlock
[[RKObjectManager sharedManager] sendObject:self.obj toResourcePath:#"/image" usingBlock:^(RKObjectLoader *loader) {
loader.targetObject = nil;
loader.delegate = self;
loader.method = RKRequestMethodPOST;
if([obj photo1]){
RKObjectMapping* serializationMapping = [[[RKObjectManager sharedManager] mappingProvider] serializationMappingForClass:[ImageModel class]];
NSError* error = nil;
NSDictionary* dictionary = [[RKObjectSerializer serializerWithObject:obj mapping:serializationMapping] serializedObject:&error];
NSLog(#"%#", dictionary);
RKParams* params = [RKParams paramsWithDictionary:dictionary];
NSData* imageData = UIImagePNGRepresentation([obj photo1]);
[params setData:imageData MIMEType:#"image/png" forParam:#"Photo1"];
loader.params = params;
}
}];
Result:
Following error appears in the log window.
2012-10-26 20:32:15.327 APP[4627:c07] response code: 500
2012-10-26 20:32:22.388 APP[4627:c07] Loaded payload: {"Message":"An error has occurred."}
2012-10-26 20:32:27.878 APP[4627:c07] W restkit.object_mapping:RKObjectMapper.m:87 Adding mapping error: Could not find an object mapping for keyPath: ''
2012-10-26 20:32:27.878 APP[4627:c07] E restkit.network:RKObjectLoader.m:231 Encountered errors during mapping: Could not find an object mapping for keyPath: ''
Method 3
Call postObject:usingBlock
[[RKObjectManager sharedManager] postObject:obj usingBlock:^(RKObjectLoader *loader){
loader.delegate = self;
RKParams* params = [RKParams params];
[params setValue:obj.description forParam:#"Description"];
[params setData:[app convertImageToNSData: [app photo1]] MIMEType:#"image/png" forParam:#"Photo1"];
loader.params = params;
}];
Result:-
Following exceptions appear in the log window.
2012-10-26 20:42:07.735 APP[4670:c07] response code: 500
2012-10-26 20:42:10.726 APP[4670:c07] Loaded payload: {"Message":"An error has occurred."}
2012-10-26 20:42:16.569 APP[4670:c07] W restkit.object_mapping:RKObjectMapper.m:87 Adding mapping error: Could not find an object mapping for keyPath: ''
2012-10-26 20:42:16.570 APP[4670:c07] E restkit.network:RKObjectLoader.m:231 Encountered errors during mapping: Could not find an object mapping for keyPath: ''
2012-10-26 20:42:16.570 APP[4670:c07] E restkit.network:RKObjectLoader.m:360 Encountered an error while attempting to map server side errors from payload: Could not find an object mapping for keyPath: ''
Apparently I am not missing anything in the mapping, but might I am . Can RestKit gurus help me any of these 3 methods to help me able to send an object having an image in one of it's fields to the server.

Restkit MultiForm Post with an Image

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

primaryKeyAttribute not working Restkit/Core Data

I just installed the framework restkit 0.9.3 and followed the Discussion Board example. Well, everything just worked great, however when I tried to use Core Data my User NSManagedObject class is duplicating even after declaring his primaryKeyAttribute (userID). For example, when I send a login request to my web-server, I return {"user":{"id":1, "username":"teste", ...}} .. but it seems to create a new row every time it invoques objectLoader:didLoadObjects.
User table:
Example code:
~ AppDelegate.m didFinishLaunching
RKManagedObjectMapping* userMapping = [RKManagedObjectMapping mappingForClass:[User class]];
userMapping.primaryKeyAttribute = #"userID";
userMapping.setDefaultValueForMissingAttributes = YES; // clear out any missing attributes (token on logout)
[userMapping mapKeyPathsToAttributes:
#"id", #"userID",
#"email", #"email",
#"username", #"username",
#"password", #"password",
nil];
[objectManager.mappingProvider registerMapping:userMapping withRootKeyPath:#"user"];
~ User.m loginWithDelegate
- (void)loginWithDelegate:(NSObject<UserAuthenticationDelegate>*)delegate {
_delegate = delegate;
[[RKObjectManager sharedManager] postObject:self delegate:self block:^(RKObjectLoader* loader) {
loader.resourcePath = #"/login";
loader.serializationMapping = [RKObjectMapping serializationMappingWithBlock:^(RKObjectMapping* mapping) {
[mapping mapAttributes:#"username", #"password", nil];
}];
}];
}
~ User.m didLoadObjects (RKObjectLoaderDelegate)
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray *)objects {
if ([objectLoader wasSentToResourcePath:#"/login"]) {
[self loginWasSuccessful];
}
NSLog(#"number of user rows: %i", [User findAll].count);
}
What am I doing wrong?
Are you correctly implementing RKManagedObjectCache? For debugging I had it simply return nil and forgot about that. A little while later I found I had duplicates also.
The cache works by fetching local objects and comparing with server returned objects. Any local objects that are not in the server response will be deleted. In earlier versions it used a fetch request but in newer versions you must manually perform the request and return actual objects.
If you return nil, it thinks this object is not in your cache and will add a duplicate. Try implementing this method:
+ (NSManagedObject *)findInstanceOfEntity:(NSEntityDescription *)entity
withPrimaryKeyAttribute:(NSString *)primaryKeyAttribute
value:(id)primaryKeyValue
inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext
For example:
+ (NSManagedObject *)findInstanceOfEntity:(NSEntityDescription *)entity
withPrimaryKeyAttribute:(NSString *)primaryKeyAttribute
value:(id)primaryKeyValue
inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext {
NSFetchRequest* request = [[NSFetchRequest alloc] init];
[request setEntity: entity];
[request setFetchLimit: 1];
[request setPredicate:[NSPredicate predicateWithFormat:#"%K = %#", primaryKeyAttribute, primaryKeyValue]];
NSArray *results = [NSManagedObject executeFetchRequest:request inContext: managedObjectContext];
if ([results count] == 0)
{
return nil;
}
return [results objectAtIndex:0];
}
I have found the problem related to targetObject (RKObjectLoader)
/**
* The target object to map results back onto. If nil, a new object instance
* for the appropriate mapping will be created. If not nil, the results will
* be used to update the targetObject's attributes and relationships.
*/
So when I set it to nil the postObject calls findOrCreateInstanceOfEntity:withPrimaryKeyAttribute:andValue
- (void)loginWithDelegate:(NSObject<UserAuthenticationDelegate>*)delegate {
_delegate = delegate;
[[RKObjectManager sharedManager] postObject:self delegate:self block:^(RKObjectLoader* loader) {
loader.resourcePath = #"/login";
loader.targetObject = nil;
loader.serializationMapping = [RKObjectMapping serializationMappingWithBlock:^(RKObjectMapping* mapping) {
[mapping mapAttributes:#"username", #"password", nil];
}];
}];
}
As of the latest RESTKit version (0.23.2) you can define the primary key like this:
[_mapping addAttributeMappingsFromDictionary:#{ #"id" : #"objectId", #"name" : #"name" }];
[_mapping setIdentificationAttributes:#[ #"objectId" ]];
Whereas objectId is you primary key on the core data object.