Specify a specific mapping for objects in JSON array in RESTkit? - ios

I'm receiving this JSON from django-rest-framework when I request a list of UserProfile objects:
[
{
"gender": 1,
"show_gender": true,
"show_real_name": true
},
{
"gender": 2,
"show_gender": true,
"show_real_name": true
}
]
But I'm at loss how to configure my mappings with Restkit. Restkit seems to expect a dictionary instead of a list, since it seems to use the dictionary's keys as "KeyPath" to identify the necessary mappings. Is there a way to specify a mapping for the received objects manually?

Got it!
// define a mapping
RKObjectMapping* userMapping = [RKObjectMapping mappingForClass:[ERUser class]];
[userMapping mapAttributes:
#"gender",
#"show_gender",
#"show_real_name",
nil];
// add the mapping anonymously
[objectManager.mappingProvider addObjectMapping:userMapping];
// tell the mapping provider which mapping to use
RKObjectMapping* articleMapping = [[RKObjectManager sharedManager].mappingProvider objectMappingForClass:[ERUser class]];
[objectManager loadObjectsAtResourcePath:#"/users" objectMapping:articleMapping delegate:self];

Related

How do I map custom error keys in Restkit?

When a user signs up successfully on our server, it responds with a 200 status code and a JSON payload like this:
{
"error": null,
"result": {
"auth": {
"created_utc": 1420740197,
"device_token": "rQZJddrbD5tyEpznb8bVKeGlHqRNGyvOgDR;tQJBkpkfAXO6DQ4lNiG17lzu6IDc0hVBfR3RrN9o0txRQIYAa6fnf5d9LNaSRDMk9LrplgkITuMC37v;;;rvG35CJvV7dWZ5TQVYUWeHwAABvKvzTRpSDw5Qg9jQrmiUHLZptegFY=76421420740197"
},
"display_name": "a",
"email": "a#a.com",
"user_id": 7642,
"username": "a"
}
}
But if a#a.com tries to sign up again, it responds with a 400 status code and a JSON payload like this:
{
"error": {
"code": 805,
"message": "there is another user with that username"
},
"result": null
}
I tried mapping the error, so that when Restkit returns an error, I get the message as well as the code. These are the ways I tried to do that:
RKObjectMapping *errorMapping = [RKObjectMapping mappingForClass:[RKErrorMessage class]];
[errorMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:#"error.message" toKeyPath:#"errorMessage"]];
RKResponseDescriptor *errorDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:errorMapping method:RKRequestMethodAny pathPattern:nil keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassClientError)];
[self.objectManager addResponseDescriptor:errorDescriptor];
This one obviously only gets the message part of the error to return with this output:
Error Domain=org.restkit.RestKit.ErrorDomain Code=1004 "there is another user with that username" UserInfo=0x7feefcf8a730 {RKObjectMapperErrorObjectsKey=(
"there is another user with that username"
), NSLocalizedDescription=there is another user with that username}
So then I tried making a subclass of RKErrorMessage:
#import "RKErrorMessage.h"
#interface TAG_RKErrorMessage : RKErrorMessage
#property (strong, nonatomic) NSNumber *errorCode;
#end
And changed the mapping to this:
RKObjectMapping *errorMapping = [RKObjectMapping mappingForClass:[TAG_RKErrorMessage class]];
[errorMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:#"error.message" toKeyPath:#"errorMessage"]];
[errorMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:#"error.code" toKeyPath:#"errorCode"]];
and that resulted in the exact same output:
Error Domain=org.restkit.RestKit.ErrorDomain Code=1004 "there is another user with that username" UserInfo=0x7fa16c627ce0 {RKObjectMapperErrorObjectsKey=(
"there is another user with that username"
), NSLocalizedDescription=there is another user with that username}
So lastly I tried this mapping to at least try to get the dictionary show up as the RKObjectMapperErrorObjectsKey of the NSError's userInfo:
RKObjectMapping *errorMapping = [RKObjectMapping mappingForClass:[RKErrorMessage class]];
[errorMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:#"error" toKeyPath:#"errorMessage"]];
And that resulted in this output:
Error Domain=org.restkit.RestKit.ErrorDomain Code=1004 "<null>" UserInfo=0x7ff8abe678e0 {RKObjectMapperErrorObjectsKey=(
(null)
), NSLocalizedDescription=<null>}
Now I'm stuck at this point. How can I map the keys of my server's error response so that I can get access to the code that is returned as well as the message as two separate values?
You've likely mapped your TAG_RKErrorMessage instance correctly. Have you attempted to extract it from the NSError you've been given? You can use the RKObjectMapperErrorObjectsKey as a key on the NSError's userInfo dictionary to get an array of all mapped error messages:
NSArray* objectMapperErrorObjectsArray = [error.userInfo objectForKey:RKObjectMapperErrorObjectsKey];
You can then loop through that array to check each error for conditions you want to respond to, for example, if you wanted to respond to a 403 response code with the error message "Invalid token", you could do:
NSArray* objectMapperErrorObjectsArray = [error.userInfo objectForKey:RKObjectMapperErrorObjectsKey];
for (RKErrorMessage* objectMapperErrorObject in objectMapperErrorObjectsArray)
{
if ([objectMapperErrorObject.errorMessage isEqual:#"Invalid token"])
{
if (operation.HTTPRequestOperation.response.statusCode == 403)
{
//Code to handle a 403 response status code when error message is "Invalid token".
}
}
}
Just a hint: if you want to use the built-in RKErrorMessage without creating any subclass, you can map all error JSON attributes to RKErrorMessage.userInfo in this way:
Objective-C:
RKObjectMapping *errorMapping = [RKObjectMapping mappingForClass:[RKErrorMessage class]];
[errorMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:nil toKeyPath:#"userInfo"]];
RKResponseDescriptor *errorDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:errorMapping method:RKRequestMethodAny pathPattern:nil keyPath:"error" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassClientError)];
Swift:
let errorMapping = RKObjectMapping(forClass: RKErrorMessage.self);
errorMapping.addPropertyMapping(RKAttributeMapping(fromKeyPath: nil, toKeyPath: "userInfo"));
let errorResponseDescriptor = RKResponseDescriptor(
mapping: errorMapping,
method: RKRequestMethod.Any,
pathPattern: nil,
keyPath: "error",
statusCodes: RKStatusCodeIndexSetForClass(UInt(RKStatusCodeClassClientError)))
);

RestKit PUT request with error mapping

Im making an app that communicates with a rails based backend server.
I already have all the server calls ready and working via RESTKit, but I am having
problems with creating error mappings for my update calls.
My response descriptors for one of my classes
RKResponseDescriptor *descriptor = [RKResponseDescriptor responseDescriptorWithMapping:[Mappings liveViewMapping]
method:RKRequestMethodAny
pathPattern:nil
keyPath:#"event_enriched"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[self addResponseDescriptor:descriptor];
RKResponseDescriptor *errorDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:[Mappings errorMapping]
method:RKRequestMethodAny
pathPattern:nil
keyPath:#"error"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassClientError)];
[self addResponseDescriptor:errorDescriptor];
Edit: I've also tried it with keyPath "errors" and 'nil' .. same results
My error mapping is quite simple:
+ (RKObjectMapping *)errorCollectionMapping {
RKObjectMapping *errorMapping = [RKObjectMapping mappingForClass:[ErrorCollection class]];
NSDictionary *mappingDictionary = #{#"error" : #"message",
#"errors" : #"messages",
};
[errorMapping addAttributeMappingsFromDictionary:mappingDictionary];
return errorMapping;
}
This is how I am trying to update my Book object
[self putObject:book
path:API_BOOK
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
if (success) {
Book *book = [mappingResult.dictionary objectForKey:#"book"];
success(book);
}
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
if (failure) failure(operation, error);
}
];
But when I get a server error, which should be handled by my response descriptor I get the following error:
Error Domain=org.restkit.RestKit.ErrorDomain Code=1001 "No mappable object representations were found at the key paths searched." UserInfo=0x7f935d07c7a0 {NSLocalizedDescription=No mappable object representations were found at the key paths searched., NSLocalizedFailureReason=The mapping operation was unable to find any nested object representations at the key paths searched: book
The representation inputted to the mapper was found to contain nested object representations at the following key paths: error, error_description
This likely indicates that you have misconfigured the key paths for your mappings., keyPath=null, DetailedErrors=(
)}
What am I doing wrong?
I already have some GET requests, that have multiple mappings (non of them is an error mapping yet) which works fine, but can't "replicate" the same behavior with error mapping
oh.. I am using restkit-0.24 :)
edit:
the error responses coming back from my rails server are in this form:
{"errors": ["error1", "error2" ... ] }
or
{"error": "error message" }
i feel so stupid right now ...
The server responses had code 200 and not the 400-499 range that RKStatusCodeClassClientError predicts.

How to add mapping between not embedded JSON objects and relationships using RestKit 0.2 and Core Data?

I'm developing my first iOS application that uses RestKit 0.2 and Core Data. Most of the time I have followed this great tutorial http://www.alexedge.co.uk/blog/2013/03/08/introduction-restkit-0-20/
however it doesn't fit my requirements since it describes embedded JSON objects and I need to create separate objects. Here is a simplified scenario of my app structure:
JSON response:
"application": [
{
"appId": "148",
"appName": …
.
.
.
(more attributes)
}
],
"image": [
{
"imgId": "308",
"appId": "148",
"iType": "screenshot",
"iPath": ..
.
.
.
},
{
"imgId": "307",
"appId": "148",
"iType": "logo",
"iPath": …
.
.
.
}
]
I have created a data model that contains two entities and set the relationship between them in a way that one application may have more than one image and one image may only belong to one application:
I have used mogenerator to create classes represented by entities.
I can successfully map the entity to Application object using the following code:
NSDictionary *parentObjectMapping = #{
#"appId" : #"appId"
};
// Map the Application object
RKEntityMapping *applicationMapping = [RKEntityMapping mappingForEntityForName:NSStringFromClass([Application class]) inManagedObjectStore:managedObjectStore];
applicationMapping.identificationAttributes = #[ #"appId" ];
[applicationMapping addAttributeMappingsFromDictionary:#{
#"appName" : #"name",
#"appCat" : #"category",
#"appType" : #"type",
#"compName" : #"companyName",
#"compWeb" : #"companyWebsite",
#"dLink" : #"downloadWebLink",
#"packageName" : #"appStoreLink",
#"osMinVer" : #"minRequirements",
#"appCost" : #"cost",
#"appDescription" : #"appDescription"
}];
[applicationMapping addAttributeMappingsFromDictionary:parentObjectMapping];
// Register our mappings with the provider
[manager addResponseDescriptorsFromArray:#[
[RKResponseDescriptor responseDescriptorWithMapping:applicationMapping
pathPattern:nil
keyPath:#"application"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]
]];
Now I have a problem when mapping the JSON to the image object with the appropriate relationship between Image and Application. How can I add the mapping between the two Entities? Is my data model correct or I'm missing something?
You want to use foreign key mapping to allow RestKit to connect the objects.
You need to add an image mapping and it should have an attribute to hold the appId. Then, you need to add a connection relationship:
[imageMapping addConnectionForRelationship:#"application" connectedBy:#{ #"appId": #"appId" }];
Which tells RestKit to use the appId attribute on the image instances to connect to application instances with the corresponding appId and to save the result in the application relationship.
I'm assuming you can't change the data structure of your "image" JSON object, so that will limit RestKit's functionality as far as automatic relational mapping goes. However, you can still set up a relationship yourself.
First, you need to add a field to your data model, appId.
Then, set up your image mapping, like this:
RKEntityMapping* imageMapping = [RKEntityMapping mappingForEntityForName:#"Image" inManagedObjectStore:objectManager.managedObjectStore];
[imageMapping addAttributeMappingsFromDictionary:#{
#"imgId" : #"imageId",
#"iPath" : #"clientPath",
#"iType" : #"type",
#"sPath" : #"serverPath",
#"appId" : #"appId"
}];
imageMapping.identificationAttributes = #[ #"imageId" ];
RKResponseDescriptor *imageResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:imageMapping pathPattern:nil keyPath:#"image" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptor:imageResponseDescriptor];
After this, your mapping should be done. To fill your local database with your objects, make this call, getObjectsAtPath:
[[RKObjectManager sharedManager] getObjectsAtPath:#"/yourWebServiceURL" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
// success block
NSLog(#"Success!");
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
//fail block
}];
This should load all of your objects into CoreData. In another place in your app, maybe even in the success block of the above call, you'll need to connect your Image objects to your Application objects. You'll need fetch requests and a for loop. Just assign every Image's "application" attribute to be the Application object which has the same appId, like this:
myImage.application = fetchedApplicationWithID;
You will then be able to look up the relationship both ways, with [myImage application] and with [myApplication images].

Restkit json error response msg from server

I have used many hours on how to solve this issue. Im using Restkit 0.9.3 with Object Mapping 2.0. All data is in JSON. I can make GET, POST, PUT and DELETE operations correctly, it's the response body I dont catch and map corretly..
So my problem is that my restful api is returning errors when something goes wrong. I want to map those errors with restkit, fx this error is returned:
{
"code": "401",
"message": "Unauthorized"
}
How do I map this json correct? I have tried lots of things and could use some guideness - or please give an example of this.
For RestKit v0.20
assuming your HTTP body is:
{"error": "..."}
you create an the mapping and descriptor:
RKObjectMapping *errorMapping = [RKObjectMapping mappingForClass:[RKErrorMessage class]];
[errorMapping addPropertyMapping: [RKAttributeMapping attributeMappingFromKeyPath:#"error" toKeyPath:#"errorMessage"]];
RKResponseDescriptor *errorResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:errorMapping pathPattern:nil keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassClientError)];
[objectManager addResponseDescriptor:errorResponseDescriptor];
and then you can access it from your failure block:
failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"errorMessage: %#", [[error userInfo] objectForKey:RKObjectMapperErrorObjectsKey]);
}
This uses the built-in RKErrorMessage class, though you can create your own custom class with additional fields.
For version 0.10.0 this response error can be mapped as follows:
RKObjectMapping *errorMapping = [RKObjectMapping mappingForClass:[RKErrorMessage class]];
[errorMapping mapKeyPath:#"message" toAttribute:#"errorMessage"];
[[[RKObjectManager sharedManager] mappingProvider] setErrorMapping:errorMapping];
Requests that return an error will call the following delegate method:
- (void)objectLoader:(RKObjectLoader *)objectLoader didFailWithError:(NSError *)error {
NSArray *errorMessages = [[error userInfo] objectForKey:RKObjectMapperErrorObjectsKey];
RKErrorMessage *errorMessage = [errorMessages objectAtIndex:0]; // First and only object in your case.
NSString *message = [errorMessage errorMessage];
NSInteger code = [[objectLoader response] statusCode];
NSLog(#"ERROR: [%d] %#", code, message); // => ERROR: [401] Unauthorized
}
Using Restkit v0.2x you can map all JSON attributes you want to the already existing RKErrorMessage.userInfo Dictionary property in this [Swift] way:
let errorMapping = RKObjectMapping(forClass: RKErrorMessage.self);
errorMapping.addPropertyMapping(RKAttributeMapping(fromKeyPath: nil, toKeyPath: "userInfo"));
let errorResponseDescriptor = RKResponseDescriptor(
mapping: errorMapping,
method: RKRequestMethod.Any,
pathPattern: nil,
keyPath: "error", //or nil, according to your json response
statusCodes: RKStatusCodeIndexSetForClass(UInt(RKStatusCodeClassClientError)))
);
So, you can map an error JSON response like this one:
{
"error": {
"message": "Error message",
"cause": "...",
"code": "my_error_code",
"url": "..."
...
}
}
And retrieve the RKErrorMessage with all attributes, in a failure closure, as follows:
failure: { (operation, error) -> Void in
if let errorMessage = error.userInfo?[RKObjectMapperErrorObjectsKey]?.firstObject as? RKErrorMessage{
let message = errorMessage.userInfo["message"] as! String;
let code = errorMessage.userInfo["code"] as! String;
...
}
}
I hope this can be helpful to someone!
if an object is returned your delegate should call this method:
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects {}
in this method you should call an instance of your "error" class and map 'code' and 'message' as necessary.
An easier way to handle errors though would be to use:
- (void)objectLoader:(RKObjectLoader*)objectLoader didFailWithError:(NSError*)error {
if(objectLoader.response.statusCode == 401)
{ ...
And show the necessary error message in that method.

RestKit Object Mapping - Parsing A Nested Array

I'm extremely stuck on mapping a simple JSON object:
{"articles":
[{"id":"354633","lat":"51.501","lng":"-0.125","type":"railwaystation","title":"Westminster tube station","url":"http:\/\/en.wikipedia.org\/w\/index.php?curid=354633","distance":"53m"},
{"id":"92601","lat":"51.5011","lng":"-0.125","type":"railwaystation","title":"17 - Westminster tube station","url":"http:\/\/en.wikipedia.org\/w\/index.php? curid=92601","distance":"62m"},
{"id":"92598","lat":"51.5011","lng":"-0.125","type":"railwaystation","title":"34 -Westminster tube station","url":"http:\/\/en.wikipedia.org\/w\/index.php?curid=92598","distance":"62m"}]
}
I'm really puzzled by the problem I'm having, since this is almost identical to the JSON in the RestKit Mapping Guide.
My code is:
self.articleMapper = [RKObjectMapping mappingForClass:[WikiArticle class]];
[self.articleMapper mapKeyPath:#"title" toAttribute:#"title"];
[[RKObjectManager sharedManager].mappingProvider setMapping:self.articleMapper forKeyPath:#"articles"];
I'm getting the following error in the log:
restkit.object_mapping:RKObjectMappingOperation.m:339 Did not find mappable attribute value keyPath 'title'
restkit.object_mapping:RKObjectMappingOperation.m:557 Mapping operation did not find any mappable content
restkit.object_mapping:RKObjectMapper.m:308 The following operations are in the queue: ()
restkit.object_mapping:RKObjectMapper.m:323 Finished performing object mapping. Results: {}
Thanks in advance for helping me out with something so simple -- I've been banging my head against it for hours!
-Alex
I suppose you call it in a way like this:
[objectManager loadObjectsAtResourcePath:url delegate:self block:^(RKObjectLoader* loader) {
loader.objectMapping = [objectManager.mappingProvider objectMappingForClass:[WikiArticle class]];
}];
Better try:
[objectManager loadObjectsAtResourcePath:url delegate:self];

Resources