Is there a way to POST large NSObject-derived object structures without having to manually specify every property and property collection to RestKit?
Here is a simple example, with a single class DABUser But imagine it contained properties which were also objects, collections, and those had more of the same to represent some larger object tree.
The class to POST:
#interface DABUser : NSObject
#property (nonatomic) int age;
#property (copy, nonatomic) NSString *name;
#end
POST a DABUser object:
RKObjectMapping *userMapping = [RKObjectMapping requestMapping];
[userMapping addAttributeMappingsFromArray:#[ #"age", #"name"]];
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:userMapping objectClass:[DABUser class] rootKeyPath:nil method:RKRequestMethodPOST];
RKObjectManager *objectManager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:#"http://localhost:3000"]];
objectManager.requestSerializationMIMEType = RKMIMETypeJSON;
[objectManager addRequestDescriptor:requestDescriptor];
DABUser *user = [[DABUser alloc] init];
user.age = 20;
user.name = #"Charlie Brown";
[objectManager postObject:user path:#"users/123" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"Success!");
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Failed!");
}];
The JSON generated from the above code being and sent via the request body is:
{ "age":20,"name":"Charlie Brown" }
When I have a large object tree, defining the property mappings can get tiresome (and error-prone), with many lines of similar code to this example's:
RKObjectMapping *userMapping = [RKObjectMapping requestMapping];
[userMapping addAttributeMappingsFromArray:#[ #"age", #"name"]];
Is there a way that I could just get RestKit to generate the JSON from the objects, without all this setup?
"When I have a large object tree, defining the property mappings can get tiresome (and error-prone), with many lines of similar code to this example's:"
I personally think this is the easiest way and a good approach.I have done object mapping to large objects with so many object mapping and multiple object linking and found this is the easiest way to deal with it correctly
Related
I have this class:
#interface MovieStatus : NSObject
#property (nonatomic, strong) NSNumber* seen;
#property (nonatomic, strong) NSNumber* watchlist;
#end
Where both properties represent optional nullable boolean values. I'm sending this object to the server using RestKit through the RKObjectManager and created the appropriate mapping. But I'm unable to skip the property from the POST data when serializing the object.
For example, this code:
RKLogConfigureByName("*", RKLogLevelTrace);
RKObjectManager* manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:#"http://www.example.com/v1"]];
manager.requestSerializationMIMEType = RKMIMETypeJSON;
RKObjectMapping* requestMapping = [RKObjectMapping requestMapping];
[requestMapping addAttributeMappingsFromArray:#[#"seen", #"watchlist"]];
RKRequestDescriptor* requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:requestMapping objectClass:[MovieStatus class] rootKeyPath:nil method:RKRequestMethodPOST];
[manager addRequestDescriptor:requestDescriptor];
RKRoute* route = [RKRoute routeWithClass:[MovieStatus class] pathPattern:#"status" method:RKRequestMethodPOST];
[manager.router.routeSet addRoute:route];
MovieStatus* status = [[MovieStatus alloc] init];
status.seen = #(YES);
[manager postObject:status path:nil parameters:nil success:nil failure:nil];
is sending the JSON:
{
"seen": true,
"watchlist": null
}
Where I'd like to send:
{
"seen": true
}
Can anyone point me out how can I achieve it?
I solved it by setting the assignsDefaultValueForMissingAttributes of the mapping to NO:
requestMapping.assignsDefaultValueForMissingAttributes = NO;
Now the JSON request doesn't contain null values.
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.
So, i m sending a POST request for the first time. I m Mapping classes and as I thought and read from the Documentation that it would work in this way:
Init RK:
- (void)initRK{
if(!manager){
manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:BASE_CONTEXT_URL]];
}
if (!reqMapping) {
reqMapping = [RKObjectMapping requestMapping];
}
}
POST Method:
// Configure a request mapping for our Article class. We want to send back title, body, and publicationDate
RKObjectMapping* deviceRequestMapping = [RKObjectMapping mappingForClass:[DeviceDTO class]];
[deviceRequestMapping addAttributeMappingsFromArray:#[ #"model", #"name", #"systemName", #"systemVersion", #"devToken" ]];
RKObjectMapping* msRequestMapping = [RKObjectMapping mappingForClass:[MemberShipDTO class]];
[msRequestMapping addAttributeMappingsFromArray:#[ #"validSince", #"validTill" ]];
RKObjectMapping* countryRequestMapping = [RKObjectMapping mappingForClass:[CountryDTO class]];
[countryRequestMapping addAttributeMappingsFromArray:#[ #"idNumberDTO", #"iso2DTO", #"short_nameDTO", #"calling_codeDTO" ]];
RKObjectMapping* userRequestMapping = [RKObjectMapping requestMapping];
[userRequestMapping addAttributeMappingsFromArray:#[ #"displayName", #"phoneNumber", #"status", #"userID" ]];
[userRequestMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:nil toKeyPath:#"device" withMapping:deviceRequestMapping]];
[userRequestMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:nil toKeyPath:#"memberShip" withMapping:msRequestMapping]];
[userRequestMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:nil toKeyPath:#"country" withMapping:countryRequestMapping]];
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:userRequestMapping objectClass:[User class] rootKeyPath:#"user"];
//Create Objects
UserDTO *user = [[UserDTO alloc]init];
user.displayName = userDTO.displayName;
user.phoneNumber = userDTO.phoneNumber;
user.status = userDTO.status;
user.userID = userDTO.userID;
user.country = userDTO.country;
DeviceDTO *device = [[DeviceDTO alloc]init];
device.name = devDTO.name;
device.systemName = devDTO.systemName;
device.systemVersion = devDTO.systemVersion;
device.model = devDTO.model;
device.devToken = [[NSUserDefaults standardUserDefaults]objectForKey:PUSHTOKEN_USER_DEFAULTS_KEY];
user.deviceInfo = device;
MemberShipDTO *ms = [[MemberShipDTO alloc]init];
ms.validSince = [NSDate date];
ms.validTill = [[UtilitieHandler new] getDateByAdd:+1 :0 :0 :0];
user.memberShipDetails = ms;
[RKMIMETypeSerialization registerClass:[RKNSJSONSerialization class] forMIMEType:#"application/json"];
[[RKObjectManager sharedManager] setRequestSerializationMIMEType:RKMIMETypeJSON];
[[RKObjectManager sharedManager] setAcceptHeaderWithMIMEType:RKMIMETypeJSON];
[[RKObjectManager sharedManager] addRequestDescriptor:requestDescriptor];
[[RKObjectManager sharedManager] postObject:user path:#"user/integrate" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult){
RKLogInfo(#"Load collection of Articles: %#", mappingResult.array);
}failure:^(RKObjectRequestOperation *operation, NSError *error) {
RKLogError(#"Operation failed with error: %#", error);
}];
So I tried different things and after i used wireshark to capture the request it returns that theres no content send. That means the mapping is not working correct. I tried a lot and nothing helped. Any advice would be great!
Here the captured packet:
POST /WAZZUUPWS/rest/service/user/integrate HTTP/1.1
Host: 192.168.2.115:8080
Accept-Encoding: gzip, deflate
Accept: application/json
Content-Length: 0
Connection: keep-alive
Accept-Language: de;q=1, en;q=0.9, fr;q=0.8, ja;q=0.7, nl;q=0.6, it;q=0.5
User-Agent: WAZZUUP!/1.0 (iPhone; iOS 6.1.4; Scale/2.00)
It might just be a typo in your question but requestDescriptor doesn't appear to be linked to the UserDTO class.
It seems like you do not have an understanding of Core Data objects yet. Objects that are persisted using Core Data are subclasses of NSManagedObject and have to be created differently. Read further on this link:
http://developer.apple.com/library/mac/#documentation/cocoa/conceptual/CoreData/Articles/cdCreateMOs.html
As for the current problem, you have to use this instead:
NSEntityDescription *entity = [NSEntityDescription entityForName:#"UserDTO" inManagedObjectContext:[RKManagedObjectStore defaultStore].mainQueueManagedObjectContext];
UserDTO *user = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];
However, if UserDTO is a subclass of NSObject, that would need to change to NSManagedObject.
My workflow is something like this - create Core Data model and use mogenerator to automatically generate the NSManagedObject class definitions. Read more about it here:http://raptureinvenice.com/getting-started-with-mogenerator/
I have a JSON end path which accepts post requests in the following format.
{'values': [
{
"date":<measurement date as Unix time stamp>
"value":<weight>
}
{
"date":<measurement date as Unix time stamp>
"value":<weight>
}
...]}
"Values" is represented by the class "EntryCollection", while each value is represented by the class "Entry". I am puzzled finding the correct way to map my objects to the JSON representation. Right now I have the following code which causes the error: "The mapping operation was unable to find any nested object representations at the key paths searched".
RKObjectMapping *entryMapping = [RKObjectMapping requestMapping];
RKObjectMapping *valuesMapping = [RKObjectMapping mappingForClass:[EntriesCollection class]];
[valuesMapping addAttributeMappingsFromDictionary:[EntryCollection attributesMapping]];
[singleEntryMapping addAttributeMappingsFromDictionary:[SingleEntry attributesMapping]];
[singleEntryMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"entries" toKeyPath:#"entries" withMapping:valuesMapping]];
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:entryMapping
objectClass:mappedClass
rootKeyPath:nil];
[self.objectManager addRequestDescriptor:requestDescriptor];
NSString *path = [self pathForPOST];
[self.objectManager postObject:weights path:path parameters:nil success:nil failure:nil];
EDIT for data structure
My data structure is simple (I suppose):
EntryCollection
- NSArray *entries (a collection of objects of type Entry)
Entry
- NSDate *date
- NSNumber *weight;
I would like to POST an EntryCollection filled with entries. The mapping of EntryCollection is "entries -> values", the one of Entry is "date -> date, weight -> value".
In any case, your JSON request payload must confirm to following data structure:
NSArray
|
|______NSDictionary ->Key: Date Value: weight
| ->Key: value Value: weight
|
|______NSDictionary ->Key: Date Value: weight
| ->Key: value Value: weight
|
|______NSDictionary ->Key: Date Value: weight
->Key: value Value: weight
Both NSArray and NSDictionary are fully compatible with JSON data format. I don't know about your underlying object structure, but ultimately this array should get posted as request payload NSData, and you will be done.
Well, if you have an issue in mapping, then you'll either have to show your model and class and mapping, or put RKLogConfigureByName("RestKit/ObjectMapping", RKLogLevelTrace); somewhere in your code and let us see output.
As an alternative, if your entities structure varies from what you want to Post to the server, you can use embedded AFNetworking client and do a simple request.
[[RKObjectManager sharedManager].HTTPClient postPath:#"" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"WHOOO");
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//
}];
I have found the solution. Turns out, I needed a relationship mapping to describe the hierarchy implied in the JSON. Since there is no type for each entity of values, I created an "empty" mapping and added the relationship mapping to it.
I also forgot to set the correct MIMEType and inverse the attributes mapping of my class. I guess a few days in restkit are needed to get a grasp of it.
RKObjectMapping *entryMapping = [RKObjectMapping requestMapping];
[entryMapping addAttributeMappingsFromDictionary:[SingleEntry attributesMapping]];
RKObjectMapping *entrySerializedMapping = [entryMapping inverseMapping];
RKRelationshipMapping *entryRelationship = [RKRelationshipMapping relationshipMappingFromKeyPath:#"entries" toKeyPath:#"values" withMapping:entrySerializedMapping];
RKObjectMapping *valueMapping = [RKObjectMapping requestMapping];
[valueMapping addPropertyMapping:valueMapping];
RKRequestDescriptor *descriptor = [RKRequestDescriptor requestDescriptorWithMapping:valueMapping objectClass:[EntriesCollection class] rootKeyPath:nil];
[self.objectManager addRequestDescriptor:descriptor];
self.objectManager.requestSerializationMIMEType = RKMIMETypeJSON;
NSString *path = [self pathForPOST];
[self.objectManager postObject:entryCollection path:path parameters:nil success:nil failure:nil];
I need to make two different types of POST coming from the User class.
//JSON Type A
{
"password":"12345",
"email":"test#gmail.com"
}
//JSON Type B
{
"user":{
"Password":"12345",
"Email":"sample#gmail.com"
}
}
I've tried to make two request descriptors and adding them to my object manager however I get the error
"Cannot add a request descriptor for the same object class as an
existing request descriptor."
My code
#interface User : NSObject
#property (nonatomic, retain) NSString * userID;
#property (nonatomic, retain) NSString * email;
#property (nonatomic, retain) NSString * password;
#property (nonatomic, retain) NSString * firstName;
#property (nonatomic, retain) NSString * lastName;
#end
- (void)setupUserMapping:(RKObjectManager *)objectManager {
// Setup user response mappings
RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[User class]];
[userMapping addAttributeMappingsFromDictionary:#{
#"ID" :#"userID",
#"Email" : #"email",
#"Password" : #"password",
#"FirstName" : #"firstName",
#"LastName" : #"lastName",
}];
RKResponseDescriptor *responseDescriptorAuthenticate = [RKResponseDescriptor responseDescriptorWithMapping:userMapping
pathPattern:#"/Authenticate"
keyPath:nil
statusCodes:[NSIndexSet indexSetWithIndex:200]];
RKResponseDescriptor *responseDescriptorRegister = [RKResponseDescriptor responseDescriptorWithMapping:userMapping
pathPattern:#"/Register"
keyPath:nil
statusCodes:[NSIndexSet indexSetWithIndex:200]];
[objectManager addResponseDescriptor:responseDescriptorRegister];
[objectManager addResponseDescriptor:responseDescriptorAuthenticate];
// Setup user request mappings
RKObjectMapping* userRequestMappingForRegister = [RKObjectMapping requestMapping];
[userRequestMappingForRegister addAttributeMappingsFromDictionary:#{
#"email" : #"Email",
#"password" : #"Password",
#"firstName" : #"FirstName",
#"lastName" : #"LastName",
}];
RKRequestDescriptor *requestDescriptorForRegister = [RKRequestDescriptor requestDescriptorWithMapping:userRequestMappingForRegister objectClass:[User class] rootKeyPath:#"user"];
RKObjectMapping* userRequestMappingForAuthenticate = [RKObjectMapping requestMapping];
[userRequestMappingForAuthenticate addAttributeMappingsFromDictionary:#{
#"userID" :#"ID",
#"email" : #"email",
#"password": #"password"
}];
RKRequestDescriptor *requestDescriptorForAuthenticate = [RKRequestDescriptor requestDescriptorWithMapping:userRequestMappingForAuthenticate objectClass:[User class] rootKeyPath:nil];
[objectManager addRequestDescriptor:requestDescriptorForRegister];
[objectManager addRequestDescriptor:requestDescriptorForAuthenticate];
}
Does anyone know how I can solve this problem without creating a separate class for these requests?
Any help is appreciated.
Thanks.
You can use a dynamic mapping to switch the serialization behaviors. If this is a common enough issue, we could conceivably add path matching to the request descriptor. I just have not had a ton of requests for such a feature.
There is an example of how to use the dynamic mapping with a request in the unit tests: https://github.com/RestKit/RestKit/blob/master/Tests/Logic/ObjectMapping/RKObjectParameterizationTest.m#L495-L534
For multiple request descriptors, I declared a new model class with the same data members as the earlier one, and then referenced it while adding the request descriptor instead of the earlier one as follow.
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[CMAGoogleUserDataModel class]];
Here the newly created class was "CMAGoogleUserDataModel"
Noted: I am not sure whether it is the optimised one or not, but it did solve my use case.