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.
Related
I'm trying to send multiple objects as an array to a Server with RestKit. Unfortunately I'm not able to do so.
Following my pretty simple objects as well as the mapping for RestKit:
Example Objects
#interface MyExampleObject : NSObject
#property NSString *key;
#property NSString *value;
#end
Array-Object holding multiple of MyExampleObject
#interface MyArray : NSObject
#property NSArray *array;
#end
Mapping
RKObjectMapping *mappingObject = [RKObjectMapping mappingForClass:[MyExampleObject class]];
[mappingObject addAttributeMappingsFromDictionary:#{
#"key" : #"key",
#"value" : #"value"
}];
RKObjectMapping *mappingArray = [RKObjectMapping mappingForClass:[MyArray class]];
[mappingArray addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"dummy" toKeyPath:#"array" mappingObject]];
If I do it this way I’ll get the following result:
{
"dummy" : [
{
"key" : "MyKey1",
"value" : "MyValue1"
},
{
"key" : "MyKey2",
"value" : "MyValue2"
}
]
}
But I want only the array without a „key“. Like this:
[
{
"key" : "MyKey1",
"value" : "MyValue1"
},
{
"key" : "MyKey2",
"value" : "MyValue2"
}
]
It seemed obvious for me to change the relationshipMappingFromKeyPath to nil. But this didn't worked (got a setObjectForKey: key cannot be nil error).
What do I have to do to send multiple MyExampleObjects to my Server as a JSON-Array?
Solution:
As Wain suggested I've removed my "Top-Mapping". Following the final mapping:
RKObjectMapping *mappingObject = [RKObjectMapping mappingForClass:[MyExampleObject class]];
[mappingObject addAttributeMappingsFromDictionary:#{
#"key" : #"key",
#"value" : #"value"
}];
And when I post the stuff to my Server I just do something like this:
NSArray *array = [NSArray arrayWithObjects:myExampleObject1, myExampleObject2, nil];
[[RKObjectManager sharedManager] postObject:array path:#"/myPath/" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
...
}
Just get rid of the MyArray object and the associated mapping and directly pass the NSArray of MyExampleObjects to the post method. RestKit will understand that it's an array of objects to map and do the right thing.
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];
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.