Question

I'm having trouble mapping a response back to an object during a post request using RestKit.

Here's the code:

Request:

// mapping for the response. response is an object: {"response":"message","success":bool}
RKObjectMapping *responseMapping = [RKObjectMapping mappingForClass:[GenericResponse class]];
    [responseMapping addAttributeMappingsFromArray:@[@"success",@"response"]];
    responseMapping.setDefaultValueForMissingAttributes = YES;
    RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:responseMapping pathPattern:@"/authenticate" keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];

    // mapping for the request body
    RKObjectMapping *requestMapping = [RKObjectMapping requestMapping];
    [requestMapping addAttributeMappingsFromArray:@[@"username", @"password"]];
    RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:requestMapping objectClass:[LoginCriteria class] rootKeyPath:nil];

    // set up the request
    RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://localhost:8080"]];
    [manager addResponseDescriptor:responseDescriptor];
    [manager addRequestDescriptor:requestDescriptor];
    [manager setRequestSerializationMIMEType:@"application/json"];

    // set up the LoginCriteria object
    LoginCriteria* loginCriteria = [LoginCriteria new];
    loginCriteria.password = @"test";
    loginCriteria.username = @"test";

    // make the request
    [manager postObject:loginCriteria path:@"/authenticate" parameters:nil
    success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
            GenericResponse *genericResponse = (GenericResponse*)mappingResult;
            NSLog(@"logged in: %@", [mappingResult array]);
    } failure:^(RKObjectRequestOperation *operation, NSError *error) {
        NSLog(@"login failed");
    }];

GenericResponse.h:

@interface GenericResponse : NSObject
@property (nonatomic) Boolean* success;
@property (nonatomic, copy) NSString* response;
@end

Log:

2012-12-17 15:44:22.890 Radiuus[8221:1703] T restkit.network:RKHTTPRequestOperation.m:139 POST 'http://localhost:8080/authenticate':
request.headers={
    Accept = "application/json";
    "Accept-Language" = "en, fr, de, ja, nl, it, es, pt, pt-PT, da, fi, nb, sv, ko, zh-Hans, zh-Hant, ru, pl, tr, uk, ar, hr, cs, el, he, ro, sk, th, id, ms, en-GB, ca, hu, vi, en-us;q=0.8";
    "Content-Type" = "application/json; charset=utf-8";
    "User-Agent" = "Radiuus/1.0 (iPhone Simulator; iOS 6.0; Scale/1.00)";
}
request.body=(null)
2012-12-17 15:44:23.244 Radiuus[8221:5d0b] T restkit.network:RKHTTPRequestOperation.m:156 POST 'http://localhost:8080/authenticate' (200):
response.headers={
    "Content-Type" = "application/json";
    Date = "Mon, 17 Dec 2012 20:44:23 GMT";
    Server = "Apache-Coyote/1.1";
    "Transfer-Encoding" = Identity;
}
response.body={"response":"authentication succeeded","success":true}
2012-12-17 15:44:23.246 Radiuus[8221:4f03] W restkit.object_mapping:RKMapperOperation.m:76 Adding mapping error: Expected an object mapping for class of type 'LoginCriteria', provider returned one for 'GenericResponse'

From the log, what's strange to me is that it seems that RestKit is expecting to deserialize the response to a LoginCriteria object, but is "failing" when it is correctly getting a GenericResponse object instead, which is of course correct.

Any help is greatly appreciated!

Was it helpful?

Solution

Thanks for your answer to your question, as it lead me in the right direction to figure out how to use one of RestKit's built-in methods to solve this issue instead of modifying the core code.

From his documentation, https://github.com/RestKit/RestKit/wiki/Object-mapping, in the Handling Multiple Root Objects in Core Data Post/Put section, he mentions that if you want to post one type of object, but receive another in the response, then you have to nil out the targetObject in the request operation. A full example is missing from his documentation, so here is the block of code I used:

RKManagedObjectRequestOperation *operation = [RKObjectManager.sharedManager appropriateObjectRequestOperationWithObject: objectToBePOSTed method:RKRequestMethodPOST path: path parameters: params];
operation.targetObject = nil;
[operation setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
    //handle success
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
    //handle failure
}];
[RKObjectManager.sharedManager enqueueObjectRequestOperation:operation];

~ Happy Holidays

OTHER TIPS

I had the same problem with RestKit. The resolve was to add mapping for the object that i was posting as a response too.

I dug into the RestKit code and found the following snippet in the mapObject function in RKMapperOperation.m:

if (NO == [[self.targetObject class] isSubclassOfClass:objectMapping.objectClass]) {
            if ([_mappingsDictionary count] == 1) {
                NSString *errorMessage = [NSString stringWithFormat:
                                          @"Expected an object mapping for class of type '%@', provider returned one for '%@'",
                                          NSStringFromClass([self.targetObject class]), NSStringFromClass(objectMapping.objectClass)];
                [self addErrorWithCode:RKMappingErrorTypeMismatch message:errorMessage keyPath:keyPath userInfo:nil];
                return nil;
            } else {
                // There is more than one mapping present. We are likely mapping secondary key paths to new objects
                destinationObject = [self objectWithMapping:mapping andData:mappableObject];
            }
        }

That first if statement checks to make sure that targetObject (the request object) has a class type that is in the hierarchy of objectMapping.objectClass (the response object). Since my request and response objects are different, this of course fails. I set a breakpoint at that line to prove it. Here's what is printed when I inspect:

(lldb) po self.targetObject
(id) $1 = 0x09fb54d0 <LoginCriteria: 0x9fb54d0>
(lldb) po objectMapping.objectClass
(Class) $2 = 0x00162c58 GenericResponse

As you can see, they are of different types, so this if succeeds and the function returns nil, leaving the response unmapped. I simply replaced return nil; with destinationObject = [self objectWithMapping:mapping andData:mappableObject]; and everything works as expected.

I am unhappy with this solution since I don't feel that the author of RestKit intended for posts to send and receive different objects. Therefore, this is a hack. I'm leaving this unanswered until a) I hear a better solution, or b) the author of RestKit makes this change himself (or accepts mine :).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top