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.
Related
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!
I'm taking a local JSON file and trying to map it into a relational mapping. It works fine without the relationship, but once I add the relationship, I get an error.
JSON:
https://gist.github.com/4675414
Code:
// Get json from destination
NSString *myJSON = [[NSString alloc] initWithContentsOfFile:contentPath encoding:NSUTF8StringEncoding error:NULL];
NSString* MIMEType = #"application/json";
NSError* parseError;
NSData *data = [myJSON dataUsingEncoding:NSUTF8StringEncoding];
id parsedData = [RKMIMETypeSerialization objectFromData:data MIMEType:MIMEType error:&parseError];
if (parsedData == nil && parseError) {
NSLog(#"Cannot parse data: %#", parseError);
}
// Setting up objectmapping for issue
RKObjectMapping *issueMapping = [RKObjectMapping mappingForClass:[Issue class]];
[issueMapping addAttributeMappingsFromDictionary:#{
#"title": #"title",
#"description": #"description",
#"cover_url": #"cover_url",
#"published_at": #"published_at",
#"issue_number": #"issue_number"
}];
//Setting up objectmapping for article
RKObjectMapping *articleMapping = [RKObjectMapping mappingForClass:[Article class]];
[articleMapping addAttributeMappingsFromDictionary:#{
#"title": #"title",
#"main_text": #"main_text",
#"article_image_url": #"article_image_url"
}];
[issueMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"articles" toKeyPath:#"articles" withMapping:articleMapping]];
Issue *issue = [[Issue alloc] init];
RKMappingOperation* mapper = [[RKMappingOperation alloc] initWithSourceObject:[parsedData objectForKey:#"issue"] destinationObject:issue mapping:issueMapping];
RKManagedObjectMappingOperationDataSource *mappingDS = [RKManagedObjectMappingOperationDataSource new];
mapper.dataSource = mappingDS;
[mapper performMapping:&parseError];
NSLog(#"Parse error: %#", parseError);
NSLog(#"Issue title: %#", issue.title);
Error:
2013-01-30 13:07:42.486 uninkd[13722:907] *** Assertion failure in -[RKManagedObjectMappingOperationDataSource mappingOperation:targetObjectForRepresentation:withMapping:inRelationship:], /Users/holgersindbaek/Projects/Uninkd/Uninkd_IOS/Pods/RestKit/Code/CoreData/RKManagedObjectMappingOperationDataSource.m:232
2013-01-30 13:07:42.487 uninkd[13722:907] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'RKManagedObjectMappingOperationDataSource must be initialized with a managed object context.'
Error if I take away the data source:
2013-01-30 13:27:32.601 uninkd[13754:907] *** Assertion failure in -[RKMappingOperation applyRelationshipMappings], /Users/holgersindbaek/Projects/Uninkd/Uninkd_IOS/Pods/RestKit/Code/ObjectMapping/RKMappingOperation.m:699
2013-01-30 13:27:32.603 uninkd[13754:907] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Cannot perform relationship mapping without a data source'
Hope you can help.
This is because RKManagedObjectMappingOperationDataSource is expecting a managed object context, this is specifically for Core Data. Are you using core data?
Forget everything I previously said and use:
#import "RKObjectMappingOperationDataSource.h"
RKObjectMappingOperationDataSource *mappingDS = [RKObjectMappingOperationDataSource new];
mapper.dataSource = mappingDS;
instead of:
RKManagedObjectMappingOperationDataSource *mappingDS = [RKManagedObjectMappingOperationDataSource new];
mapper.dataSource = mappingDS;
A hint, don't use any classes that say ManagedObject if you're not using CoreData. This is referring to a ManagedObject in a ManagedObjectContext which is unique to CoreData. That being said, core data + restkit is awesome and you should check it out.
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.
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.