RestKit: How does one post an array of objects?
Question summary:
Consider a class SyncObject
that is KVC-compliant with properties such as: time
, someValue
, lastChange
, uuid
.
Consider an NSArray
containing exclusively instances of SyncObject
.
I need to submit the array to the server as a JSON array.
How would one submit this array to the server using HTTP POST using RestKit?
Example array:
[
{
"time": "14:45 10/21/2011",
"someValue": "15",
"lastChange": "14:45 10/21/2011",
"uuid": "0b07c510-f4c8-11e0-be50-0800200c9a66"
},
{
"time": "14:50 10/21/2011",
"someValue": "62",
"lastChange": "14:51 10/21/2011",
"uuid": "1a6d4480-f4c8-11e0-be50-0800200c9a66"
}
]
Details
I have an array of objects that I need to the server as JSON. It seems to me that RestKit is the easiest way to do this: I'm trying to avoid converting objects into a set of NSDictionary
objects, and then using some JSON encoder to get JSON which I can POST
to the server.
So, having created the array, and having set up the mapping for the class of objects stored in the array, I naturally try to POST
to the server.
RKObjectManager* mgr = [RKObjectManager objectManagerWithBaseURL:@"http://localhost/someweb/api/"];
mgr.serializationMIMEType = RKMIMETypeFormURLEncoded;
mgr.client.username = @"username";
mgr.client.password = @"password";
RKObjectMapping* mapping = [RKObjectMapping mappingForClass:[NSMutableDictionary class]];
[mapping mapKeyPath: @"time" toAttribute:@"time" ];
[mapping mapKeyPath: @"someValue" toAttribute:@"someValue" ];
[mapping mapKeyPath: @"lastChange" toAttribute:@"lastChange" ];
[mapping mapKeyPath: @"uuid" toAttribute:@"uuid" ];
RKObjectMapping* mappingForSerialization = [mapping inverseMapping];
[mgr.mappingProvider setSerializationMapping:mappingForSerialization
forClass:[NSManagedObject class]];
[mgr.router routeClass:[NSManagedObject class] toResourcePath:@"/sync" forMethod:RKRequestMethodPOST];
[mgr postObject:array delegate:nil/*self*/];
However, this is what I get out:
2011-10-11 14:57:51.769 AppConnect[1974:6e0b] *** Terminating app due to uncaught exception '(null)', reason: 'Unable to find a routable path for object of type '__NSArrayI' for HTTP Method 'POST''
Apparently, RestKit does not know how to handle NSArray
s.
How does one post an array of objects using RestKit?
I've tried something different: I replaced the last line with a manual send through RKObjectLoader
.
//[mgr postObject:[NSMutableDictionary dictionaryWithObject:array forKey:@"data"] delegate:nil/*self*/];
NSString* syncPath = @"/sync";
RKObjectLoader * objectLoader = [mgr objectLoaderWithResourcePath:syncPath delegate:self];
objectLoader.serializationMIMEType = RKMIMETypeJSON;
objectLoader.method = RKRequestMethodPOST;
//objectLoader.objectClass = [NSManagedObject class];
//objectLoader.managedObjectStore = mgr.objectStore;
objectLoader.params = [NSDictionary dictionaryWithObject:array
forKey:@"MyData"];
[objectLoader send];
Unfortunately, this does not apply mapping of any sort, and instead transmits an array of objects' descriptions. Setting serializationMIMEType
also does not affect the structure of transmitted contents, and params
are always transmitted as application/x-www-form-urlencoded
.
I also tried assigning serialization mapping and passing the object as targetObject
and sourceObject
(this seems to be what RestKit does internally in -[RKObjectManager postObject:delegate:]
).
RKObjectLoader * objectLoader = [mgr objectLoaderWithResourcePath:syncPath delegate:self];
objectLoader.method = RKRequestMethodPOST;
//objectLoader.objectClass = [NSManagedObject class];
//objectLoader.managedObjectStore = mgr.objectStore;
objectLoader.params = [NSMutableDictionary dictionaryWithObject:array
forKey:@"MyData"];
objectLoader.serializationMapping = mapping;
objectLoader.serializationMIMEType = RKMIMETypeJSON;
objectLoader.sourceObject = objectLoader.params;
objectLoader.targetObject = objectLoader.params;
[objectLoader send];
Unfortunately, no mapping occurs this way:
2011-10-12 12:36:48.143 MyProject[5119:207] D restkit.network:RKObjectLoader.m:290 POST or PUT request for source object {
MyData = (
"<NSManagedObject: 0x5935430> (entity: SomeRecord; id: 0x5934da0 <x-coredata://64DF9977-DA50-4FCD-8C20-4132E58439BF/SomeRecord/p1> ; data: <fault>)",
"<NSManagedObject: 0x5935730> (entity: SomeRecord; id: 0x5934db0 <x-coredata://64DF9977-DA50-4FCD-8C20-4132E58439BF/SomeRecord/p2> ; data: <fault>)"
);
}, serializing to MIME Type application/json for transport...
2011-10-12 12:36:48.143 MyProject[5119:207] D restkit.object_mapping:RKObjectMappingOperation.m:428 Starting mapping operation...
2011-10-12 12:36:48.145 MyProject[5119:207] T restkit.object_mapping:RKObjectMappingOperation.m:291 Did not find mappable attribute value keyPath 'time'
2011-10-12 12:36:48.145 MyProject[5119:207] T restkit.object_mapping:RKObjectMappingOperation.m:291 Did not find mappable attribute value keyPath 'someValue'
2011-10-12 12:36:48.145 MyProject[5119:207] T restkit.object_mapping:RKObjectMappingOperation.m:291 Did not find mappable attribute value keyPath 'lastChange'
2011-10-12 12:36:48.145 MyProject[5119:207] T restkit.object_mapping:RKObjectMappingOperation.m:291 Did not find mappable attribute value keyPath 'uuid'
2011-10-12 12:36:48.145 MyProject[5119:207] D restkit.object_mapping:RKObjectMappingOperation.m:448 Mapping operation did not find any mappable content
2011-10-12 12:36:48.146 MyProject[5119:207] T restkit.network:RKRequest.m:211 Prepared POST URLRequest '<NSMutableURLRequest http://someurl/api/sync?request=provide_key>'. HTTP Headers: {
Accept = "application/json";
"Content-Length" = 0;
}. HTTP Body: .
Solution 1:
The restkit does not fund routable path for NSArray, because you defined your routing for NSManagedObject
class. You probably want to create a custom class, say MySyncEntity
that holds the ivars you define in your mapping. Then, you create your mapping like this:
RKObjectMapping* mapping = [RKObjectMapping mappingForClass:[MySyncEntity class]];
....
[myManager setSerializationMIMEType:RKMIMETypeJSON];
[[myManager router] routeClass:[MySyncEntity class] toResourcePath:@"/sync"];
then you should be able to post your object to the API backend as JSON object.
Further clarification:
In this case, we want to post an array of NSManagedObject
instances into a JSON based API. To do that we need to create a sync entity, that holds the objects in an array:
@interface MySyncEntity : NSObject {}
@property (nonatomic, retain) NSArray* mySyncArray;
...
@end
The mySyncArray
will hold the payload we'd like to submit to the rest backend. Then, we create appropriate mapping for both NSManagedObject
that will be sent in mySyncArray
and the MySyncEntity
entity itself.
RKObjectManager *manager = [RKObjectManager objectManagerWithBaseURL:kBaseUrl];
...
RKObjectMapping *mngObjMapping = [RKObjectMapping mappingForClass:[NSManagedObject class]];
[mngObjMapping mapKeyPath: @"time" toAttribute:@"time"];
[mngObjMapping mapKeyPath: @"recordLevel" toAttribute:@"recordLevel"];
.... //map as many properties as you wish
[[manager mappingProvider] setSerializationMapping:[mngObjMapping inverseMapping]
forClass:[NSManagedObject class]];
//now, we create mapping for the MySyncEntity
RKObjectMapping *syncEntityMapping = [RKObjectMapping mappingForClass:[MySyncEntity class]];
[syncEntityMapping mapKeyPath:@"mySyncArray" toRelationship:@"mySyncArray" withMapping:mngObjMapping];
[[manager mappingProvider] setSerializationMapping:[syncEntityMapping inverseMapping]
forClass:[MySyncEntity class]];
Now with the mappings defined we can post the object to the server
[manager postObject:mySyncInstance delegate:nil];
The contents of mySyncInstance
array will be mapped according to mngObjMapping
and sent to defined rest endpoint.
Solution 2:
As a further clarification, I'd like to point out that in mja's answer the key thing is
[syncEntityMapping mapKeyPath:@"mySyncArray" toRelationship:@"mySyncArray" withMapping:mngObjMapping];
This says "the mySyncArray
keypath is an array which contains objects that should be mapped according to mngObjMapping
".