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.
Related
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;
}
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;
}
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 map JSON associative array of objects with RestKit(iOS).
It looks like object with properties 135,145,423 and objects on it.
{
"135": {
"name" : "Object1",
"property1" : "Value1",
"anotherProperty1" : "Value2"
},
"145": {
"name": "Object2",
"property1" : "Value1",
"anotherProperty1" : "Value2"
},
"423": {
"name": "Object3",
"property1" : "Value1",
"anotherProperty1" : "Value2"
}
}
I've got mapping for single object that works.
Mapping performs to CoreData.
The only solution i have is to convert associative array to ordinary array and place number to "id" field, but i don't think it's elegant solution.
Is there any right way to perform such mapping directly with RestKit?
Here's the solution for my situation.
NSIndexSet *statusCodes = RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful); // Anything in 2xx
// 1. Create dynamic mapping
RKDynamicMapping* dynamicMapping = [[RKDynamicMapping alloc] init];
// 2. Process every entry separately
dynamicMapping.forceCollectionMapping = YES;
// 3. Set mappings for every object
[dynamicMapping setObjectMappingForRepresentationBlock:^RKObjectMapping *(id representation) {
// 4. Mapping to Core Data (Can be replaced with RKObjectMapping if there's no need of CodeData)
RKEntityMapping *singleRouteMapping = [RKEntityMapping mappingForEntityForName:#"Object" inManagedObjectStore:managedObjectStore];
// 5. Walking through all keys (but with dynamicMapping.forceCollectionMapping = YES) there'll be only one. It's better to refactor it.
for (NSString *key in representation) {
// 6. Set mappings for every property exect 'id'
[singleRouteMapping addAttributeMappingsFromDictionary:#{
[NSString stringWithFormat: #"%#.name", key]: #"name",
[NSString stringWithFormat: #"%#.property1", key]: #"property1",
[NSString stringWithFormat: #"%#.anotherProperty1", key]: #"anotherProperty1"
}];
}
// 7. Map 'id' property at last
[singleRouteMapping addAttributeMappingFromKeyOfRepresentationToAttribute: #"id"];
return singleRouteMapping;
}];
RKResponseDescriptor *pluralDescriptor = [RKResponseDescriptor responseDescriptorWithMapping: dynamicMapping
pathPattern: #"/api/objects"
keyPath: nil
statusCodes: statusCodes];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.somesite.com/api/objects"]];
RKManagedObjectRequestOperation *operation = [[RKManagedObjectRequestOperation alloc] initWithRequest:request responseDescriptors:#[pluralDescriptor]];
You need to use a dynamic mapping, where the mapping is created specifically for the received keys in the dictionary. You don't say what the destination object is or what your mappings are so this is a general example (for Core Data, but can be changed to plain objects):
RKDynamicMapping* dynamicMapping = [[RKDynamicMapping alloc] init];
[dynamicMapping setObjectMappingForRepresentationBlock:^RKObjectMapping *(id representation) {
RKEntityMapping* typeMapping = [RKEntityMapping mappingForEntityForName:#"..." inManagedObjectStore:objectStore];
for (NSString *key in representation) {
NSDictionary *type = [representation objectForKey:key];
[typeMapping addAttributeMappingsFromDictionary:#{
[NSString stringWithFormat:#"%#.name", key]: #"name"}];
}
return typeMapping;
}];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:dynamicMapping
pathPattern:...
keyPath:nil
statusCodes:[NSIndexSet indexSetWithIndex:200]];
This basically strips out the numbers and throws them away. You could include them if required by configuring the dynamic mapping.