I'm attempting to map the following object:
{"walletAccepted":false,"creditCardTypesAccepted":["visa","mastercard","discover","americanexpress"],"paypalAccepted":false}
To an object with identical attributes. However, the ResourcePath is dynamic, in that it's along the lines of /paymentmethods/zone/:internalZoneCode
Every attempt I've made at mapping it always ends up with "Encountered errors during mapping: Could not find an object mapping for keyPath: ''"
I think the issue is that there is no root key/key path. I've also attempted the following with no luck:
RKURL *rkUrl = [RKURL URLWithBaseURL:[RKClient sharedClient].baseURL resourcePath:[NSString stringWithFormat:#"/paymentoptions/zone/%#", zoneNumber]];
RKObjectLoader* loader = [[RKObjectLoader alloc] initWithURL:rkUrl mappingProvider:[RKObjectManager sharedManager].mappingProvider];
loader.method = RKRequestMethodGET;
loader.delegate = self;
loader.objectMapping = [[RKObjectManager sharedManager].mappingProvider objectMappingForClass:[PMZonePaymentMethods class]];
[loader send];
I found the way to make it happen is to configure the mapping, and add the mapping to the object manager without a key path or resourcek
So like this:
RKObjectMapping* zonePaymentMethodsMapping = [RKObjectMapping mappingForClass:[PMZonePaymentMethods class]];
[zonePaymentMethodsMapping mapKeyPath:#"walletAccepted" toAttribute:#"walletAvailable"];
[zonePaymentMethodsMapping mapKeyPath:#"creditCardTypesAccepted" toAttribute:#"cards"];
[zonePaymentMethodsMapping mapKeyPath:#"paypalAccepted" toAttribute:#"paypalAvailable"];
[[RKObjectManager sharedManager].mappingProvider addObjectMapping:zonePaymentMethodsMapping];
and use this to make the request:
RKURL *rkUrl = [RKURL URLWithBaseURL:[RKClient sharedClient].baseURL resourcePath:[NSString stringWithFormat:#"/paymentoptions/zone/%#", zoneNumber]];
RKObjectLoader* loader = [[RKObjectLoader alloc] initWithURL:rkUrl mappingProvider: [RKObjectManager sharedManager].mappingProvider];
loader.method = RKRequestMethodGET;
loader.delegate = self;
loader.objectMapping = [[RKObjectManager sharedManager].mappingProvider objectMappingForClass:[PMZonePaymentMethods class]];
[loader send];
Related
I'll try to explain how I want the mapping done:
TOP lvl json object contains Players object which is an array of Player objects
each Player object contains an array, I want each of those objects in the array to be of an Event object (custom object).
now since I have a mapping of the Player object and i'm getting the array filled, tho instead of Event objects(which is what I want), i'm getting NSDictionary objects. thing is that I do have a mapping of my Event class. my issue is getting the RestKit to map these into the array.
I've tried adding responseDescriptors of an Event class tho i've had no luck.
Here is the Player object mapping
RKObjectMapping* playerMapping = [RKObjectMapping mappingForClass:[Player class]];
[playerMapping addAttributeMappingsFromDictionary:#{
...more here
#"activeEvents" : #"activeEvents"
}];
here is the request method
NSURL *taskURL = [NSURL URLWithString:kAppWebApiURLPath];
// Set object manager with base url
RKObjectManager *objectManager = [RKObjectManager sharedManager];
objectManager = [RKObjectManager managerWithBaseURL:taskURL];
objectManager.requestSerializationMIMEType = RKMIMETypeJSON;
[objectManager.HTTPClient setDefaultHeader:#"Authorization" value:kAppWebAPIKey];
[objectManager.HTTPClient setDefaultHeader:#"Content-Type" value:#"application/json"];
RKRequestDescriptor * requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:[MappingProvider inverseLoginMapping] objectClass:[LoginInfo class] rootKeyPath:nil method:RKRequestMethodPOST];
[objectManager addRequestDescriptor:requestDescriptor];
RKResponseDescriptor *playersResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:[MappingProvider playerMapping] method:RKRequestMethodGET pathPattern:nil keyPath:#"players" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptor:playersResponseDescriptor];
NSLog(#"%#",loginInfo.iOSDeviceToken);
[objectManager postObject:loginInfo path:kAppWebApiLoginPath parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult)
{}...
Update, I now need one step further of mapping, My player object contains an array of events which I successfully mapped using
[playerMapping addRelationshipMappingWithSourceKeyPath:#"activeEvents" mapping:[MappingProvider eventMapping]];
yet now each of those Event objects contains an array of Players, so its like Players -> Events -> Players.
Here is the Mapping for both Event and Player objects :
RKObjectMapping* eventMapping = [RKObjectMapping mappingForClass:[Event class]];
[eventMapping addAttributeMappingsFromDictionary:#{
#"stuffhere" : #"stuffz"
}];
RKObjectMapping* playerMapping = [RKObjectMapping mappingForClass:[Player class]];
[playerMapping addAttributeMappingsFromDictionary:#{
#"name": #"name",
#"activeEvents" : #"activeEvents"
}];
[eventMapping addRelationshipMappingWithSourceKeyPath:#"activeEvents/players" mapping:playerMapping];
now I don't get a recursive function, but how do I state in code to make that relationship
mapping of the json array to assign to my local array property ?
Remove #"activeEvents" : #"activeEvents" from the mapping and replace it with:
[playerMapping addRelationshipMappingWithSourceKeyPath:#"activeEvents" mapping:eventMapping];
You should also only have one response descriptor because the data is nested.
I am a beginner to RESTKIT and have only just recently tested it out on foursquare public api from ray's tutorial http://www.raywenderlich.com/13097/intro-to-restkit-tutorial.
Although i get the gist of it, there are some part which i do not understand, and would like pointer for it, so that i can consume my own web service.
- (void)viewDidLoad
{
[super viewDidLoad];
RKURL *baseURL = [RKURL URLWithBaseURLString:#"https://api.Foursquare.com/v2"];
RKObjectManager *objectManager = [RKObjectManager objectManagerWithBaseURL:baseURL];
objectManager.client.baseURL = baseURL;
RKObjectMapping *venueMapping = [RKObjectMapping mappingForClass:[Venue class]];
[venueMapping mapKeyPathsToAttributes:#"name", #"name", nil];
[objectManager.mappingProvider setMapping:venueMapping forKeyPath:#"response.venues"];
[self sendRequest];
}
how do I change
[venueMapping mapKeyPathsToAttributes:#"name", #"name", nil];
[objectManager.mappingProvider setMapping:venueMapping forKeyPath:#"response.venues"];
to accommodate my own webMethod? (my webMethod is shown below)
Currently, I upload my file to IIS for testing purpose, and am using the IP for web service. (I am constantly changing work area, so I designate it as myIPAddress for easier communication)
-- My service code (changed EDIT:now return JSON)
[WebMethod]
[ScriptMethod( ResponseFormat = ResponseFormat.Json)]
public void testTextJSON()
{
string text = "Testing for Json!";
List<string> arrayList = new List<string>();
arrayList.Add(text);
JavaScriptSerializer js = new JavaScriptSerializer();
string name = js.Serialize(arrayList);
Context.Response.Write(name);
}
return - ["Testing for Json!"]
EDIT- what I changed currently for viewDidLoad and sendRequest to test for my own service
- (void)viewDidLoad
{
[super viewDidLoad];
RKURL *baseURL = [RKURL URLWithBaseURLString:#"http://192.168.1.12"];
RKObjectManager *objectManager = [RKObjectManager objectManagerWithBaseURL:baseURL];
objectManager.client.baseURL = baseURL;
RKObjectMapping *venueMapping = [RKObjectMapping mappingForClass:[venue class]];
[venueMapping mapKeyPathsToAttributes:#"name", #"name", nil];
[self sendRequest];
}
and
- (void)sendRequest
{
RKObjectManager *objectManager = [RKObjectManager sharedManager];
RKURL *URL = [RKURL URLWithBaseURL:[objectManager baseURL] resourcePath:#"/webService/webService1.asmx/testTextJSON"];
objectManager.acceptMIMEType = RKMIMETypeJSON;
objectManager.serializationMIMEType = RKMIMETypeJSON;
[[RKParserRegistry sharedRegistry] setParserClass:[RKJSONParserJSONKit class] forMIMEType:#"text/plain"];
[objectManager loadObjectsAtResourcePath:[NSString stringWithFormat:#"%#", [URL resourcePath]] delegate:self];
}
EDIT n+1 - here are some of my error message, maybe someone can tell me what went wrong?
2012-11-25 06:49:20.925 fourSquareAPI[352:12e03] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<__NSCFString 0x8354430> valueForUndefinedKey:]: this class is not key value coding-compliant for the key venues.'
If I remove [objectManager.mappingProvider setMapping:venueMapping forKeyPath:#"venues"];
I would get
2012-11-25 06:52:47.495 fourSquareAPI[368:11603] response code: 200
2012-11-25 06:52:47.499 fourSquareAPI[368:12e03] W restkit.object_mapping:RKObjectMapper.m:87 Adding mapping error: Could not find an object mapping for keyPath: ''
2012-11-25 06:52:47.499 fourSquareAPI[368:12e03] E restkit.network:RKObjectLoader.m:231 Encountered errors during mapping: Could not find an object mapping for keyPath: ''
2012-11-25 06:52:47.502 fourSquareAPI[368:11603] Error: Could not find an object mapping for keyPath: ''
Can someone please teach me what to do?? Any help would be greatly appreciated, I would really like to learn how to use RESTKIT to consume a webservice.
The key path response.venues is a path to the attribute with respect to the server response that is in JSON or XML format. In this case, the server returns "response" which has the key "venues". This is most likely a list of venues that you would apply the mapping to. You have to adapt that based on your server response.
If you want to handle a specific value before the mapping, use this function:
- (void)objectLoader:(RKObjectLoader *)loader willMapData:(inout __autoreleasing id *)mappableData; {
if([*mappableData valueForKey:#"result"] != nil){
[*mappableData removeObjectForKey:#"result"]; //This key won't be mapped now
}
}
I'm working a long time on this problem, but couldn't find a solution
that fits my needs.
The problem is, how could I load data and map them to the relationship
without loading the whole structure
Simple example:
We got some birds:
{
"birds":[{
"bird":{"id":"1","value":"LaLeLu"},
"bird":{"id":"2","value":"LeLeLa"},
...
}]
}
This could get mapped by something like this:
RKManagedObjectMapping *birdMapping = [RKManagedObjectMapping
mappingForClass:[Bird class]];
menuMapping.primaryKeyAttribute = #"identifier";
[menuMapping mapKeyPath:#"value" toAttribute:#"value"];
[menuMapping mapKeyPath:#"id" toAttribute:#"identifier"];
[[[RKObjectManager sharedManager] mappingProvider]
setMappingForKeyPath:"birds.bird"];
Works great by now.
Now every bird could have a lot of comments - but I don't wanne load
all these comments with the first request.
Comments should get loaded when users clicks the specific bird.
So I request:
NSString *resourcePath = [NSString stringWithFormat:#"/birds/%#/
comments", myBird.id]
[[RKObjectManager sharedManager]
loadObjectsAtResourcePath:resourcePath];
I could change the response that it fits the needs of RestKit - but
what are the needs?
{
"comments":[{
"comment"{"id":"1","value":"Comment1","bird_id":"1"}
}]
}
And now I don't have an idea how to map this response.
Mapping the comments without any relation to the birds is no problem:
RKManagedObjectMapping *commentMapping = [RKManagedObjectMapping
mappingForClass:[Comment class]];
menuMapping.primaryKeyAttribute = #"identifier";
[menuMapping mapKeyPath:#"value" toAttribute:#"value"];
[menuMapping mapKeyPath:#"id" toAttribute:#"identifier"];
[[[RKObjectManager sharedManager] mappingProvider]
setMappingForKeyPath:"comments.comment"];
Hope someone understands my problem and could help
For all who ware interested in the solution:
RestKit 0.10.0 fix the problem:
RKManagedObjectMapping *commentMapping = [RKManagedObjectMapping
mappingForClass:[Comment class]];
commentMapping.primaryKeyAttribute = #"identifier";
[commentMapping mapKeyPath:#"value" toAttribute:#"value"];
[commentMapping mapKeyPath:#"id" toAttribute:#"identifier"];
// Here starts the relevant part:
[commentMapping mapKeyPath:#"bird_id" to Attribute:#"bird_id"];
[commentMapping mapRelationship:#"bird" withMapping:birdMapping];
[commentMapping connectRelationship:#"bird" withObjectPropertyForPrimaryKeyAttribute:"bird_id"]
[[[RKObjectManager sharedManager] mappingProvider]
setMapping:commentMapping ForKeyPath:"comments.comment"];
I'm just starting to use Restkit and am trying to send a regular POST request "serverUrl/account/authenticate.xml?OPTIONS" to get a response as XML. This is the code I use to call the URL:
-(void) function{
// OBJECT MANAGER
RKObjectManager* manager = [RKObjectManager objectManagerWithBaseURL:serverUrl];
[manager.router routeClass:[CBUser class] toResourcePath:#"/account/authenticate" forMethod:RKRequestMethodPOST];
// OBJECT MAPPING
RKObjectMapping* mapping = [RKObjectMapping mappingForClass:[CBUser class]];
mapping = [manager.mappingProvider objectMappingForKeyPath:#"user"];
[mapping mapKeyPath:#"id" toAttribute:#"userId"];
[manager loadObjectsAtResourcePath:str objectMapping:mapping delegate:self];
}
// DELEGATE
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects {
CBUser* user = [objects objectAtIndex:0];
NSLog(#"Loaded Contact ID #%# -> Firstname: %#, Lastname: %#", user.userId, user.firstname, user.lastname);
}
- (void)objectLoader:(RKObjectLoader*)objectLoader didFailWithError:(NSError*)error {
NSLog(#"Encountered an error: %#", error);
}
Looking at the server log it seems it receives a GET request instead of a POST even though I'm using the "RKRequestMethodPOST" option.
Here is my error form my logs:
Started GET "/account/authenticate.xml?commit=Login&authenticity_token=iPhone&user%5Bpassword=XXXXX&user%5Bemail=XXXXXXX%5D&user%5Bdevice_token=XXXXXXX" for 192.168.106.30 at 2012-01-25 19:20:03 -0800
AbstractController::ActionNotFound (The action 'show' could not be found for AccountController):
Rendered /Users/Guillaume/.rvm/gems/ruby-1.9.2-p290/gems/actionpack-3.0.9/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb within rescues/layout (1.2ms)
What am I missing?
Hopefully this code snippet of my app will help. You setup your mappings, say in your delegate. Note the "forMethod"
RKObjectRouter *router;
RKObjectMapping* createAccountSerializationMapping = [RKObjectMapping mappingForClass:[Cr eateAccount class]];
[createAccountSerializationMapping mapAttributes:#"email", #"pwd", #"uname", nil];
[[RKObjectManager sharedManager].mappingProvider setSerializationMapping:createAccountSerializationMapping forClass:[CreateAccount class]];
router = [RKObjectRouter new] ;
[router routeClass:[CreateAccount class] toResourcePath:#"/registration/rest/users/create_account" forMethod:RKRequestMethodPOST];
[RKObjectManager sharedManager].router = router;
and then later, when you want to post an object
[[RKObjectManager sharedManager] postObject:user delegate:self];
Since I couldn't figure out how to set up two different POST resource paths for the same class , I tried manually creating the RKObjectLoader request but it seems to keep sending a GET request instead of a POST even though I've set the method to POST. Here is my code
User *user = [[User alloc] init];
user.uname = uname;
user.pwd = pwd;
RKObjectManager *svc = [RKObjectManager sharedManager];
RKObjectMapping* mapping = [svc.mappingProvider objectMappingForClass:[User class]];
// what I was using before I needed two post resource paths//[svc postObject:user mapResponseWith:mapping delegate:self];
RKObjectLoader *loader = [svc loadObjectsAtResourcePath:authResourcePath objectMapping:mapping delegate:self];
[loader setMethod:RKRequestMethodPOST];
loader.userData = [NSNumber numberWithInt:RequestLogin];
loader.params = [NSDictionary dictionaryWithObjectsAndKeys:
uname, #"uname",
pwd, #"pwd",
nil];
[loader setSourceObject:user];
[loader send];
[user release];
In cases where you have more than one path to POST or PUT to, the easiest thing to do is use the block form of the postObject: invocation and specify the destination resourcePath yourself:
[[RKObjectManager sharedManager] postObject:foo delegate:bar block:^(RKObjectLoader *loader) {
loader.resourcePath = #"/my/destinationPath";
}];
We may introduce a named route concept at some point that would let you disambiguate the routes using names, but for now its purely based on the HTTP verb.
Note that you do NOT and cannot register the secondary path on the router -- you are sidestepping it completely for the secondary path.
In order to complete Blake Watters answer if the different route need different objectMapping you will need to do:
[[RKObjectManager sharedManager] postObject:query delegate:saveJobQueryHandler block:^(RKObjectLoader* loader) {
loader.objectMapping = NEW_MAPPING;
loader.resourcePath = #"/other/url";
loader.targetObject = nil; // Important
}];
For more information about loader.targetObject = nil; read sendObject:delegate:block: