RestKit 0.20 nested Object mapping (path to object tree different) - ios

I got a problem with mapping a nested object value.
I got two objects with the following properties:
a)
class Input
#property NSString value;
#property NSString title;
b)
class Profile
#property Input myAwesomeInput;
..so a Profile contains an Input object. When I mapp the objects with RestKit (0.20) I get sth. like this:
{ myAwesomeInput_test:{"value":"xyz","title":"a title"}}
What I wanna achieve is:
{myAwesomeInput_test:"xyz"}
So I don't want to map "Input" but just the Input.value. Is that even possible?
At the moment my code looks like this:
RKObjectMapping* inputMapping = [RKObjectMapping requestMapping];
[inputMapping addAttributeMappingsFromArray:#[#"value"]];
RKRequestDescriptor *reqDescInput = [RKRequestDescriptor requestDescriptorWithMapping:inputMapping objectClass:[Input class] rootKeyPath:nil];
RKObjectMapping* searchProfile = [RKObjectMapping requestMapping];
RKRequestDescriptor *reqDescSearchProfile = [RKRequestDescriptor requestDescriptorWithMapping:searchProfile objectClass:[SearchProfile class] rootKeyPath:nil];
[searchProfile addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"myAwesomeInput" toKeyPath:#"myAwesomeInput_test" withMapping:inputMapping]];
EDIT: (solved)
Ok I solved it. Hope it's the way people should do it. You can directly address from within the dictionary.
RKObjectMapping* searchProfile = [RKObjectMapping requestMapping];
[aeSearchProfile addAttributeMappingsFromDictionary:#{
#"myAwesomeInput.value": #"myAwesomeInput_test"
}];
RKRequestDescriptor *reqDescSearchProfile = [RKRequestDescriptor requestDescriptorWithMapping:searchProfile objectClass:[SearchProfile class] rootKeyPath:nil];

Use keypaths rather than multiple mappings.
Try this:
RKObjectMapping* searchProfile = [RKObjectMapping requestMapping];
[searchProfile addAttributeMappingsFromDictionary:#{ #"myAwesomeInput.value" : #"myAwesomeInput_test" }];
RKRequestDescriptor *reqDescSearchProfile = [RKRequestDescriptor requestDescriptorWithMapping:searchProfile objectClass:[SearchProfile class] rootKeyPath:nil];

Related

How do I ignore empty properties on posted objects using restkit

I am reviving an old project that originally used RestKit 0.10, and now am using RestKit 0.24. The old version still works, but unfortunately RestKit 0.10 is not 64-bit compatible and hence does not submit to the AppStore (and it is certainly time to update anyway).
I cannot get an object to post correctly. In RestKit 0.10, properties without values were not sent to the server, whereas it seems in RestKit 0.20 they are. I have tried explicitly setting assignsDefaultValueForMissingAttributes to NO, but it doesn't seem to make a difference.
The server expects the following format:
{"response": {"assessment_id":"1","time_taken":"60"},
"answer": [
{"question_number": 1, "answer_value": 3},
{"question_number": 2, "answer_value": 2},
{"question_number": 3, "answer_value": 1},
]
}
I have set up an object CompletedAssessment which contains a Response object and an array of Answer objects. (Note that when these objects are received from the server, many more properties need to be received than need to be sent).
#interface CompletedAssessment : NSObject {
Response *response;
NSArray *answers;
}
#interface Answer : NSObject {
NSNumber *identifier;
NSNumber *responseId;
NSNumber *questionNumber;
NSString *answerHistory;
NSString *answerValue;
NSString *answerText;
NSNumber *timeTaken;
}
#interface Response : NSObject {
NSNumber *identifier;
NSNumber *assessmentId;
NSNumber *timeTaken;
NSNumber *clientId;
NSString *assessmentShortName;
NSString *score;
NSString *interpretation;
NSString *dateCreated;
NSString *localTime;
}
I set the mapping up as follows:
RKObjectMapping *answerMapping = [RKObjectMapping mappingForClass:[Answer class]];
answerMapping.assignsDefaultValueForMissingAttributes = NO;
[answerMapping addAttributeMappingsFromDictionary:#{
#"id": #"identifier",
#"response_id": #"responseId",
#"question_number": #"questionNumber",
#"answer_history": #"answerHistory",
#"answer_value": #"answerValue",
#"answer_text": #"answerText",
#"time_taken": #"timeTaken"
}];
RKObjectMapping *responseMapping = [RKObjectMapping mappingForClass:[Response class]];
responseMapping.assignsDefaultValueForMissingAttributes = NO;
[responseMapping addAttributeMappingsFromDictionary:#{
#"id": #"identifier",
#"client_id": #"clientId",
#"assessment_id": #"assessmentId",
#"time_taken": #"timeTaken",
#"score": #"score",
#"assessment_short_name": #"assessmentShortName",
#"interpretation": #"interpretation",
#"created": #"dateCreated",
#"local_time": #"localTime"
}];
RKObjectMapping *completedAssessmentMapping = [RKObjectMapping mappingForClass:[CompletedAssessment class]];
completedAssessmentMapping.assignsDefaultValueForMissingAttributes = NO;
[completedAssessmentMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"response" toKeyPath:#"response" withMapping:responseMapping]];
[completedAssessmentMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"answer" toKeyPath:#"answers" withMapping:answerMapping]];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:completedAssessmentMapping method:RKRequestMethodGET pathPattern:nil keyPath:#"data.completedAssessment" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[[RKObjectManager sharedManager] addResponseDescriptor:responseDescriptor];
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:[completedAssessmentMapping inverseMapping] objectClass:[CompletedAssessment class] rootKeyPath:nil method:RKRequestMethodPOST];
[[RKObjectManager sharedManager] addRequestDescriptor:requestDescriptor];
[objectManager.router.routeSet addRoute:[RKRoute
routeWithClass:[CompletedAssessment class]
pathPattern:#"clients/:response.clientId/responses"
method:RKRequestMethodPOST]] ;
Logging reveals the end JSON appears in this format:
{"response":
{"interpretation":null,"id":null,"score":null,"client_id":15,"local_time":"2015-8-6 13:8:34","time_taken":5,"assessment_short_name":null,"assessment_id":8,"created":null},
"answer":[
{"answer_value":"0","id":null,"answer_text":null,"answer_history":null,"time_taken":null,"response_id":null,"question_number":1},
{"answer_value":"1","id":null,"answer_text":null,"answer_history":null,"time_taken":null,"response_id":null,"question_number":2}
]}
And RestKit logging confirms the null mapping:
restkit.object_mapping:RKMappingOperation.m:873 Mapped relationship object from keyPath 'response' to 'response'. Value: {
"assessment_id" = 8;
"assessment_short_name" = "<null>";
"client_id" = 15;
created = "<null>";
id = "<null>";
interpretation = "<null>";
"local_time" = "2015-8-6 13:8:34";
score = "<null>";
"time_taken" = 5;
}
restkit.object_mapping:RKMappingOperation.m:715 Mapped attribute value from keyPath 'identifier' to 'id'. Value: (null)
...
Please help!
You are creating a new mapping calling [selfCompletedAssessmentMapping inverseMapping] in this line:
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:[selfCompletedAssessmentMapping inverseMapping] objectClass:[CompletedAssessment class] rootKeyPath:nil method:RKRequestMethodPOST];
Save it to a variable and assign assignsDefaultValueForMissingAttributes to NO before creating the descriptor:
RKObjectMapping *requestMapping = [selfCompletedAssessmentMapping inverseMapping];
requestMapping.assignsDefaultValueForMissingAttributes = NO;
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:requestMapping objectClass:[CompletedAssessment class] rootKeyPath:nil method:RKRequestMethodPOST];

RestKit Request Mapping - Post Array of objects

I am trying to serialize a SyncRewardDataRequestModel in my response body. "an_id" serializes fine. However, the SyncRewardDataInputModel objects within the NSArray always serializes to a empty NSArray. I have confirmed that I am passing a correct value in my NSArray - Does anyone see what is incorrect with my mapping?
Classes:
#interface SyncRewardDataInputModel : NSObject
#property (nonatomic,copy) NSNumber *test_id;
#end
#interface SyncRewardDataRequestModel : NSObject
#property (nonatomic,strong) NSArray *syncRewardDataInputs;
#property (nonatomic,copy) NSNumber *an_id;
#end
The following is my response descriptor:
//Populate mapping
RKObjectMapping *requestMapping = [RKObjectMapping requestMapping]; // objectClass == NSMutableDictionary
[requestMapping addAttributeMappingsFromDictionary:#{#"an_id": #"an_id"}];
RKObjectMapping *syncRewardDataInputsMapping = [RKObjectMapping mappingForClass:[SyncRewardDataInputModel class]];
[syncRewardDataInputsMapping addAttributeMappingsFromDictionary:#{#"test_id": #"test_id"}];
//Combine
RKRelationshipMapping *arrayRelation = [RKRelationshipMapping relationshipMappingFromKeyPath:#"syncRewardDataInputs" toKeyPath:#"downloadCardResponseDTOs" withMapping:syncRewardDataInputsMapping];
[requestMapping addPropertyMapping:arrayRelation];
//Put it in a request
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:requestMapping objectClass:[SyncRewardDataRequestModel class] rootKeyPath:nil method:RKRequestMethodAny];
return requestDescriptor;
This line:
RKObjectMapping *syncRewardDataInputsMapping = [RKObjectMapping mappingForClass:[SyncRewardDataInputModel class]];
should be:
RKObjectMapping *syncRewardDataInputsMapping = [RKObjectMapping requestMapping];
because for a request you are always trying to map to an NSMutableDictionary so that JSON can be generated from it for transmission.
Often you will have a mapping used for the received data, linked to a response descriptor, and you can use inverseMapping on that to generate the mapping to be used for your request descriptor.

RestKit - Post or Put an entity containing nested entities

I have an iOS app that uses RestKit to sync between my Core Data model and a Rails API.
I have Game and Team entities in my Core Data model. A Game has a to-many relationship to Teams. I am trying to update the 'score' attribute of the Teams, and then I am trying to run the putObject method on my RKObjectManager by sending in the Game. The scores of the teams are not updating on the server.
If I change an attribute of the Game, e.g. the 'state', and then send in the Game with putObject, it works correctly.
Is it possible to update more than one object with putObject given that the object has nested objects inside of it? Or do I need to run putObject on the Team when I update its 'score' attribute?
Here is my mapping code for Games.
Class itemClass = [Game class];
RKEntityMapping *mapping = [RKEntityMapping
mappingForEntityForName:#"Game"
inManagedObjectStore:managedObjectStore];
mapping.identificationAttributes = #[#"gameID"];
NSDictionary *standardDict = #{#"id": #"gameID",
#"created_at": #"createdAt",
#"updated_at": #"updatedAt"};
NSDictionary *gameDict = #{#"league_id": #"leagueID",
#"location_id": #"locationID",
#"state": #"state",
//.... more attributes....
};
[mapping addAttributeMappingsFromDictionary:standardDict];
[mapping addAttributeMappingsFromDictionary:gameDict];
[mapping addConnectionForRelationship:#"league" connectedBy:#{#"leagueID": #"leagueID"}];
[mapping addConnectionForRelationship:#"location" connectedBy:#{#"locationID": #"locationID"}];
NSIndexSet *statusCodes = RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful);
NSString *keyPath = nil;
NSString *itemsPath = #"games/:gameID";
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:mapping
method:RKRequestMethodAny
pathPattern:itemsPath
keyPath:keyPath
statusCodes:statusCodes];
[manager addResponseDescriptor:responseDescriptor];
NSString *itemPath = #"game";
RKEntityMapping *requestMapping = [mapping inverseMapping];
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:requestMapping
objectClass:itemClass
rootKeyPath:itemPath
method:RKRequestMethodAny];
[manager addRequestDescriptor:requestDescriptor];
//route for manipulating with existing object
RKRoute *itemRoute = [RKRoute routeWithClass:itemClass pathPattern:#"games/:gameID" method:RKRequestMethodAny];
itemRoute.shouldEscapePath = YES;
[manager.router.routeSet addRoutes:#[itemRoute]];
The mapping for a Team is written basically the exact same way, except a Team has a connection to a Game based on the Game's 'gameID.' So --> [mapping addConnectionForRelationship:#"game" connectedBy:#{#"gameID": #"gameID"}];
You are using foreign key mappings on your response mapping:
[mapping addConnectionForRelationship:#"league" connectedBy:#{#"leagueID": #"leagueID"}];
[mapping addConnectionForRelationship:#"location" connectedBy:#{#"locationID": #"locationID"}];
and these are not reversed when you use inverseMapping (because they don't contain enough information to create the inverse).
So, your requestMapping needs to be explicitly updated to include a relationship mapping to tell RestKit to process the relationship and how to map the relationship contents into the resulting JSON.

RestKit Mapping of Array with Ids for PUT Method

I'm stuck on the question of how to build up my objects and mapping to achieve something like this when putting data via the PUT-Method: "lastChanges/confirm"
The above PUT-Request accepts a body like this to confirm synchronization of box ids:
{ "synchronized_boxes": [47292,someOtherBoxId,..] }
I have tried building an Object like this:
#interface RPConfirmSync : NSObject
#property (nonatomic, retain) NSArray *synchronized_boxes;
#end
Before I send this Object I add some NSNumber Objects to the array.
The mapping I set up looks like this:
RKObjectMapping *confirmMapping = [RKObjectMapping mappingForClass:[RPConfirmSync class]];
[confirmMapping addAttributeMappingsFromArray:#[#"synchronized_boxes"]];
RKObjectMapping *requestMapping = [confirmMapping inverseMapping];
NSString *pathPattern = [NSString stringWithFormat:#"lastsync/confirm"];
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:requestMapping objectClass:[RPConfirmSync class] rootKeyPath:nil];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:confirmMapping pathPattern:pathPattern keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[self.objectManager addRequestDescriptor:requestDescriptor];
[self.objectManager addResponseDescriptor:responseDescriptor];
Now, when I execute the above PUT-Request and look at the request body, the RestKit Debug information shows me something weird like this:
request.body=synchronized_boxes[]=47292 //being sent to the server !ERROR!
which should be
request.body=synchronized_boxes[47292]
How do I have to set up my Object or is there something wrong with the mapping? I'm really stuck here, although I guess the answer is straight forward.

RestKit and Restlet JSON key-value

In RestKit for mapping a class to JSON key-value we use,
RKObjectMapping * userMapping = [RKObjectMapping mappingForClass:[User class]];
[userMapping mapKeyPath:#"PrimaryKey" toAttribute:#"id"];
[[RKObjectManager sharedManager].mappingProvider setMapping:usereMapping forKeyPath:#"clientUser"];
However in android, if restlet is used, we just have to add #JsonProperty("PrimaryKey"), when declaring the variable in the class. And the Key value mapping is done.
Is there a simpler way for iOS restkit similar to android restlet?
Thanks in advance.
You can do it like this in ios
RKObjectMapping* articleMapping = [RKObjectMapping mappingForClass:[Article class]];
[articleMapping addAttributeMappingsFromDictionary:#{
#"title": #"title",
#"body": #"body",
#"author": #"author",
#"publication_date": #"publicationDate"
}];
And
// Create our new Author mapping
RKObjectMapping* authorMapping = [RKObjectMapping mappingForClass:[Author class] ];
// NOTE: When your source and destination key paths are symmetrical, you can use addAttributesFromArray: as a shortcut instead of addAttributesFromDictionary:
[authorMapping addAttributeMappingsFromArray:#[ #"name", #"email" ]];
REFER HERE

Resources