I have route
[RKRoute routeWithClass:[BBComment class] pathPattern:#"/comments" method:RKRequestMethodPOST]
And response descriptor
[RKResponseDescriptor responseDescriptorWithMapping:[BBComment apiMapping] method:RKRequestMethodPOST pathPattern:#"/comments" keyPath:nil statusCodes:successCodes]
Here route when post object
<RKClassRoute: 0x7fe2da8d65e0 objectClass=BBComment method=(POST) pathPattern=comment>
Printing description of routingMetadata:
{
query = {
parameters = {
"post_id" = 3205;
text = ds;
};
};
routing = {
parameters = {
};
route = "<RKClassRoute: 0x7fe2da8d65e0 objectClass=BBComment method=(POST) pathPattern=comment>";
};
}
Before post a new Comment i create temp object
BBComment *wComm = [BBComment MR_createEntity];
and then
[[RKObjectManager sharedManager] postObject:wComm path:nil parameters:sendCommentData success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
BBLog(#"ret:%#", wComm);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
}];
Firstly:
In RestKit operation create
NSSet *temporaryObjects = [[managedObjectContext insertedObjects] filteredSetUsingPredicate:temporaryObjectsPredicate];
it's empty
But i think it's not a key.
After response from server i got two different objects wComm and from responseResult
Why? I think what RestKit update given object instead creating a new one?
Here objects before and after POST
(lldb) po [wComm objectID]
0xd00000000050001c <x-coredata://C721341D-CAA2-4240-809B-D3F2D45032B5/BBComment/p20>
(lldb) po [mappingResult.firstObject objectID]
0xd00000000054001c <x-coredata://C721341D-CAA2-4240-809B-D3F2D45032B5/BBComment/p21>
I try save object before post (like a RestKit do if object is temp) - nothing changes.
I think maybe different contexts? But no
(lldb) po wComm.managedObjectContext
<NSManagedObjectContext: 0x7fa442c42080>
(lldb) po [mappingResult.firstObject managedObjectContext]
<NSManagedObjectContext: 0x7fa442c42080>
UPDATE
Not, i get object, not an array like here
What is the relationship between the post object, in the RestKit postObject method, and the RKMappingResult it returns?
I found why it's not work. Because targetObject not set in operation
Problem in this function
if (RKDoesArrayOfResponseDescriptorsContainMappingForClass(self.responseDescriptors, [object class])) operation.targetObject = object;
It's work by ALL descriptors, not only for matching responseDescriptors, but ALL WTF?
And it's return NO for object.
Mapping for this object is RKDynamicMapping.
PROBLEM FOUND
Here problem: not use setObjectMappingForRepresentationBlock
Because u will get
po [mapping objectMappings]
<__NSArray0 0x7fcdd8d0ad30>(
)
And function of RKMappingGraphVisitor will not work.
SOLUTION 1
Get RKOperation and set manually targetObject. But i think it's not correct.
BETTER SOLUTION
Use something like
+ (RKDynamicMapping *)apiMapping {
RKDynamicMapping *mapping = [RKDynamicMapping new];
[mapping addMatcher:[RKObjectMappingMatcher matcherWithKeyPath:#"type" expectedValue:BBMessageType.plain objectMapping:[self plainTypeMapping]]];
return mapping;
}
Related
I have this JSON (an array of dictionaries):
[
{"id":"BTCLTC","last":"89.767","high":"96.185","low":"25.000","bid":"89.729","ask":"91.320","volume":"29.78918","scale":3},
{"id":"BTCUSD","last":"443.799","high":"444.092","low":"394.570","bid":"439.110","ask":"446.760","volume":"4.68266","scale":3},
{"id":"BTCXRP","last":"98101.500","high":"98101.500","low":"86000.000","bid":"94999.050","ask":"97499.900","volume":"21.66779","scale":3}
]
And I would like to use an RKObjectMappingMatcher to match against "id":"BTCUSD" for example.
EDIT:
Thanks to Wain's comment below, using an RKObjectMapping was enough for KVC validation to work. The validation method is automatically called by RestKit.
My code thus far looks like:
RKObjectMapping *defaultMapping = [RKObjectMapping mappingForClass:class];
NSDictionary *attributeMappings = [query objectForKey:#"attributeMappings"];
[defaultMapping addAttributeMappingsFromDictionary:attributeMappings];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:dynamicMapping method:RKRequestMethodFromString(#"GET") pathPattern:#"/api/v1/markets" keyPath:#"" statusCodes:[NSIndexSet indexSetWithIndex:RKStatusCodeClassSuccessful]];
RKObjectManager *manager = [RKObjectManager managerWithBaseURL:url];
[manager addResponseDescriptor:responseDescriptor];
I've added what I think is necessary for validation for an NSObject.
- (BOOL)validateCurrencyConversionID:(id *)ioValue error:(NSError **)outError
{
if ([(NSString*)*ioValue length] == 0)
{
*outError = [NSError errorWithDomain:RKErrorDomain code:100 userInfo:#{#"description":#"Empty string"}];
return NO;
}
else if ([((NSString*)*ioValue) rangeOfString:#"USD"].location == NSNotFound)
{
*outError = [NSError errorWithDomain:RKErrorDomain code:101 userInfo:#{#"description":#"Doesn't contain USD"}];
return NO;
}
return YES;
}
Don't use this approach, because you can't suitably index into the array and as a result you're processing an array where you expect a string.
Instead, allow the mapping to run for each and then use KVC validation to abort the mapping for the items you don't want.
The problem is that I need to remove the attribute #"unitprice" entirely from the payload when the value is nil, but keep it in there if it has a value in the request. So the payload for OrderLine would look as follows accordingly: #{"id":#"orderlineId", #"unitprice":#"unitprice"} OR #{"id":#"orderlineId"} Please note that the mapping is a one-to-many relationship. Is it possible to do this? Your help is really appreciated thank you!
/*
requestDescriptor
*/
+(RKObjectMapping*) getSalesOrderMapping:(RKRequestMethod)method {
RKEntityMapping *requestMapping = [RKEntityMapping mappingForEntityForName:#"SalesOrder"
inManagedObjectStore:[RKManagedObjectStore defaultStore]];
RKEntityMapping *orderLinesMapping = [RKEntityMapping mappingForEntityForName:#"OrderLine"
inManagedObjectStore:[RKManagedObjectStore defaultStore]];
requestMapping.identificationAttributes = #[ #"salesOrderId" ];
NSMutableDictionary *attributeMappings = [NSMutableDictionary dictionaryWithDictionary:#{
#"id": #"salesOrderId",
}];
NSDictionary *orderLineAttributeMappings = #{
#"id": #"orderlineId",
#"unitprice": #"unitPrice"
};
[orderLinesMapping addAttributeMappingsFromDictionary:orderLineAttributeMappings];
[requestMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"lines"
toKeyPath:#"salesOrderToOrderLines"
withMapping:orderLinesMapping]];
return requestMapping;
}
Set assignsDefaultValueForMissingAttributes to NO on the mapping. You shouldn't need 2 different mappings (unless you do want to include nil in the JSON for some attributes).
It seems impossible to implement dynamic request descriptor with 1-to-many relationship. So I had to build NSMutableDictionary manually by checking against nil value and then add the properties with its values like the following
[objectManager postObject:nil
path:#"/salesorder"
parameters:[self customSalesOrderRequestMapping]
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {... }
- (NSMutableDictionary *) customSalesOrderRequestMapping {
customSalesOrderAttributeDictionary = [NSMutableDictionary new];
if (selectedSalesOrder.salesOrderId) [customSalesOrderAttributeDictionary addEntriesFromDictionary:#{#"id": selectedSalesOrder.salesOrderId}];
if (selectedSalesOrder.salesOrderToOrderLines.count > 0) {
NSMutableArray *orderLinesMutableArray = [NSMutableArray new];
for (OrderLine *orderLine in selectedSalesOrder.salesOrderToOrderLines)
{
NSMutableDictionary *orderLineMutableDictionary = [NSMutableDictionary new];
if (orderLine.orderlineId) [orderLineMutableDictionary addEntriesFromDictionary:#{#"id": orderLine.orderlineId}];
if (orderLine.unitPrice) [orderLineMutableDictionary addEntriesFromDictionary:#{#"unitprice": orderLine.unitPrice}];
[orderLinesMutableArray addObject:orderLineMutableDictionary];
}
[customSalesOrderAttributeDictionary addEntriesFromDictionary:#{#"lines": orderLinesMutableArray}];
}
return customSalesOrderAttributeDictionary;
}
Below is the JSON response that I get back:
{
"notificationId": 121,
"activities": [
143,
149]
}
Below is the mapping:
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[NSMutableDictionary class]];
[mapping addAttributeMappingsFromDictionary:#{#"notificationId" : #"notificationId",
#"activities": #"activities"}];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:mapping
method:RKRequestMethodGET
pathPattern:nil
keyPath:#""
statusCodes:statusCodeSet];
[self.objectManager postObject:invitation path:#"/notifications" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSString *notificationId1 = [mappingResult.dictionary valueForKey:#"notificationId"];
NSArray *activitesArray1 = [mappingResult.dictionary valueForKey:#"activities"];
NSString *notificationId2 = [mappingResult.dictionary objectForKey:#"notificationId"];
NSArray *activitesArray2 = [mappingResult.dictionary objectForKey:#"activities"];
NSLog(#"notificaiton ID %#", notificationId1);
NSLog(#"Activites %#", activitesArray1);
NSLog(#"notification ID %#", notificationId2);
NSLog(#"Activites %#", activitesArray2);
NSLog(#"Activites Array %#", activitesArray);
NSLog(#"mappingResult Dictionary %#", mappingResult.dictionary);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Failusre");
}];
}
Log:
2014-04-21 19:17:51.273 App[57446:4403] I restkit.network:RKObjectRequestOperation.m:250 POST 'http://www.domain.com/notifications' (200 OK / 1 objects) [request=0.2371s mapping=0.0035s total=0.2446s]
2014-04-21 19:17:51.274 App[57446:60b] notification ID (null)
2014-04-21 19:17:51.274 App[57446:60b] Activites (null)
2014-04-21 19:17:51.274 App[57446:60b] notification ID (null)
2014-04-21 19:17:51.274 App[57446:60b] Activites (null)
2014-04-21 19:17:51.274 App[57446:60b] Activites Array (
)
2014-04-21 19:17:51.275 App[57446:60b] mappingResult Dictionary {
"" = {
activities = (
143,
149
);
notificationId = 121;
};
}
Why are all the values Null? How can I get the values of notificationId back as an NSString and activitiesin an array ? Can this be done without creating a mapping class?
Because your response descriptor is wrong. It should be:
[RKResponseDescriptor responseDescriptorWithMapping:mapping
method:RKRequestMethodPOST
pathPattern:#"/notifications"
keyPath:nil
statusCodes:statusCodeSet];
so that it matches the request you're sending (a POST, not a GET is the main issue).
For some unknow reason, it looks like, there is a wrapper object in the dictionary, that has key value #"" so first extract that and then everything else. Activities itself do look like array, so simply:
NSArray *activities = [[mappingResult.dictionary objectForKey:#""] objectForKey:#"activities"];
However that notification looks tricky, because it probably is a number. So if it is a consistent type, use:
NSNumber *notificationID = [[mappingResult.dictionary objectForKey:#""] objectForKey:#"notificationId"];
NSSring *notificationIDString = [notificationID stringValue];
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
I'm trying to use RestKit 0.20-pre3 together with RKXMLReaderSerialization and XMLReader in order to map a XML response from a WebService like this:
<ArrayOfAddressBookItem xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://tempuri.org/">
<AddressBookItem>
<CommonName>xxxxxxxxxx</CommonName>
<OU>xxxxxx</OU>
<Name>xxxxxx</Name>
<LastName>xxxxxxxxxx</LastName>
<Service>xxxxxxxxxx</Service>
<Email>xxxxxxxxxxxx</Email>
<InternalPhoneNumber>xxxxxxxxxxx</InternalPhoneNumber>
<ExternalPhoneNumber>xxxxxxxxxxx</ExternalPhoneNumber>
<Mobile>xxxxxxxxxxx</Mobile>
<Street>xxxxxxxxxxx</Street>
<PostalCode>xxxxxxxxxxx</PostalCode>
<City>xxxxxxx</City>
<County>xxxxxxxxx</County>
<SupervisorCommonName>
xxxxxxxxxxxxx
</SupervisorCommonName>
<SupervisorLastName>xxxxxxxxxx</SupervisorLastName>
</AddressBookItem>
<AddressBookItem>
<CommonName>yyyyyyyyyyyy</CommonName>
<OU>
yyyyyyyyyyyyy
</OU>
<Name>yyyyyyyyy</Name>
<LastName>yyyyyyyy</LastName>
<Service>yyyyyyyyyy</Service>
<Email>yyyyyyyyyy</Email>
<InternalPhoneNumber>yyyyyyyyy</InternalPhoneNumber>
<ExternalPhoneNumber>yyyyyyyy</ExternalPhoneNumber>
<Street>yyyyyyyyyyy</Street>
<PostalCode>yyyyyy</PostalCode>
<City>yyyyyy</City>
<County>yyyyyyyy</County>
<SupervisorCommonName>
yyyyyyyyyyy
</SupervisorCommonName>
<SupervisorLastName>yyyyyy</SupervisorLastName>
</AddressBookItem>
<AddressBookItem>
....
</AddressBookItem>
<AddressBookItem>
</ArrayOfAddressBookItem>
In the App Delegation code:
[RKMIMETypeSerialization registerClass:[RKXMLReaderSerialization class] forMIMEType:#"application/xml"];
AFHTTPClient *httpClient = [AFHTTPClient clientWithBaseURL:[NSURL URLWithString:#"http://thehostaddress/mywebserviceurl/"]];
httpClient.parameterEncoding = AFFormURLParameterEncoding;
RKObjectManager *objManager = [[RKObjectManager alloc] initWithHTTPClient:httpClient];
[objManager setAcceptHeaderWithMIMEType:RKMIMETypeTextXML];
objManager.requestSerializationMIMEType = RKMIMETypeTextXML;
RKObjectMapping *personMapping = [RKObjectMapping mappingForClass:[PersonItem class]];
[personMapping addAttributeMappingsFromDictionary:#{#"CommonName" : #"commonName", #"OU" : #"ou", #"Name" : #"name", #"LastName" : #"lastName", #"Service" : #"service", #"Email" : #"eMail", #"InternalPhoneNumber" : #"internalPhoneNumber", #"ExternalPhoneNumber" : #"externalPhoneNumber", #"Mobile" : #"mobilePhoneNumber", #"Street" : #"street", #"PostalCode" : #"postalCode", #"City" : #"city", #"County" : #"county", #"SupervisorCommonName" : #"supervisorCommonName", #"SupervisorLastName" : #"supervisorLastName"}];
RKResponseDescriptor *peopleResponse = [RKResponseDescriptor responseDescriptorWithMapping:personMapping pathPattern:#"/mywebserviceurl/" keyPath:#"ArrayOfAddressBookItem" statusCodes:[NSIndexSet indexSetWithIndex:200]];
[objManager addResponseDescriptor:peopleResponse];
later, when I want to get the objects:
[objManager getObjectsAtPath:#"/mywebserviceurl/" parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"SUCCESS: %#", mappingResult);
_items = [[mappingResult array] mutableCopy];
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"ERROR: %#", error);
}];
I can see that the mapper gets the correct number of the Array elements, but for each field of the object, I cannot retrieve the values:
2012-12-10 19:02:53.370 GenPeople2[14240:1703] T restkit.object_mapping:RKMappingOperation.m:341 Mapping attribute value keyPath 'CommonName' to 'commonName'
2012-12-10 19:02:53.370 GenPeople2[14240:1703] T restkit.object_mapping:RKMappingOperation.m:228 Found transformable value at keyPath 'CommonName'. Transforming from type '__NSDictionaryM' to 'NSString'
2012-12-10 19:02:53.371 GenPeople2[14240:1703] T restkit.object_mapping:RKMappingOperation.m:360 Skipped mapping of attribute value from keyPath 'CommonName to keyPath 'commonName' -- value is unchanged ((null))
and the result in my objects are null values.
I saw that the XML parser gives me back this:
2012-12-10 19:02:53.371 GenPeople2[14240:1703] D restkit.object_mapping:RKMapperOperation.m:218 Asked to map source
object {
City = {
text = thecity;
};
CommonName = {
text = thename;
};
County = {
text = thecounty;
};
and so on....
How to map correctly the values in order to permit RestKit to retrieve the values in the NSDictionary for each field ?
Thank Richard for your feedback, but it didn't work as I would like.
Really simpler: using nested keypaths in source object to map, worked as a charm:
RKObjectMapping *abItemMapping = [RKObjectMapping mappingForClass:[AddressBookItem class]];
[abItemMapping addAttributeMappingsFromDictionary:#{#"CommonName.text" : #"commonName", #"OU.text" : #"ou", #"Name.text" : #"name", #"LastName.text" : #"lastName", #"Service.text" : #"service", #"Email.text" : #"email"}];
I map the child nodes of an element as their own objects. So OU for example would be represented by a mapping and relationship of it's own:
RKObjectMapping *baseValueMapping = [RKObjectMapping mappingForClass:[CHRValue class]];
[baseValueMapping addAttributeMappingsFromDictionary:#{#"text" : #"value"}];
RKRelationshipMapping *ouRelation = [RKRelationshipMapping relationshipMappingFromKeyPath:#"OU" toKeyPath:#"ou" withMapping:baseValueMapping];
RKRelationshipMapping *nameRelation = [RKRelationshipMapping relationshipMappingFromKeyPath:#"Name" toKeyPath:#"name" withMapping:baseValueMapping];
[addressBookMapping addPropertyMapping:ouRelation];
[addressBookMapping addPropertyMapping:nameRelation];
Where CHRValue has a property named "value" which is an NSString. Note you need to use "text" to refer to the value of a node.