I'm new in RestKit.
Can someone help me with mapping. I have JSON and I need to read and save data from JSON. How I can do it?
{"data": {
"viewer": {
"themes": {
"edges": [
{
"node": {
"id": "61c39",
"name": "ff"
}
}
{
"node": {
"id": "dd95af4b-"",
"name": "growth",
}
}
]
}
}
}
}
Here is a part of my code for mapping:
// Defines mapping for DefaultThemes
RKEntityMapping *mappingDefaultTheme = [RKEntityMapping mappingForEntityForName:#"DefaultThemeData"
inManagedObjectStore:self.managedObjectStore];
mappingDefaultTheme.identificationAttributes = #[#"themeId"];
[mappingDefaultTheme addAttributeMappingsFromDictionary:#{ #"node.id" : #"themeId", #"node.name" : #"name"}];
RKResponseDescriptor *themeDefaultListResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:mappingDefaultTheme
method:RKRequestMethodGET
pathPattern:#"graphql"
keyPath:#"edges.node"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
I have error when I running the app:
Error: No mappable object representations were found at the key paths searched.
So I need to know, how I should do data mapping?
The key path data.viwer.themes.edges.node is too deep because you're trying to drill down into an array and that isn't possible. The longest key path you can have is data.viwer.themes.edges and then the mapping will process each of the objects containing 'nodes'.
Here is my solution:
RKObjectMapping* chartDataDetailMapping = [RKObjectMapping mappingForClass:[MADefaultThemeData class]];
[chartDataDetailMapping addAttributeMappingsFromDictionary:#{#"id": #"themeId",
#"name": #"name"}];
RKObjectMapping* chartDataMapping = [RKObjectMapping mappingForClass:[MANode class]];
[chartDataMapping addAttributeMappingsFromDictionary:#{#"edges":#"edges"}];
RKObjectMapping* alertInstanceMapping = [RKObjectMapping mappingForClass:[MADefaultThemeList class]];
[alertInstanceMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"edges"
toKeyPath:#"edges"
withMapping:chartDataMapping]];
[chartDataMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"node"
toKeyPath:#"themeData"
withMapping:chartDataDetailMapping]];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:alertInstanceMapping
method:RKRequestMethodPOST
pathPattern:#"graphql"
keyPath:#"data.viewer.themes"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[self addResponseDescriptor:responseDescriptor];
Currently NSNumber get mapped to string, e.g.:
#interface Blah : NSObject
#property NSNumber *num;
#end
will get mapped to
{
num: "5"
}
instead of
{
num: 5
}
Does anyone know how to correctly map NSNumber to int, and NOT string?
Update:
RKObjectMapping *requestMapping = [RKObjectMapping requestMapping];
[requestMapping addAttributeMappingsFromArray:#[#"num"]];
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:requestMapping objectClass:[MyClass class] rootKeyPath:nil method:RKRequestMethodPOST];
[_manager addRequestDescriptor:requestDescriptor];
So, i m using RestKit version 0.20 and i m successfully sending a POST request as JSON. My Server backend (Java REST WS (Jersey)) is mapping everything right, aswell Restkit.
My Problem is now that i m sending a different Object back as that I have Post. I have following mapping setup in RestKit:
- (void)createUserAccount:(DeviceDTO *)devDTO :(UserDTO *)userDTO block:(void (^)(id))block{
id errorCode __block;
// Configure a request mapping for our Article class. We want to send back title, body, and publicationDate
RKObjectMapping* deviceRequestMapping = [RKObjectMapping requestMapping];
[deviceRequestMapping addAttributeMappingsFromArray:#[ #"model", #"name", #"systemName", #"systemVersion", #"devToken" ]];
RKObjectMapping* msRequestMapping = [RKObjectMapping requestMapping];
[msRequestMapping addAttributeMappingsFromArray:#[ #"validSince", #"validTill" ]];
RKObjectMapping* countryRequestMapping = [RKObjectMapping requestMapping];
[countryRequestMapping addAttributeMappingsFromArray:#[ #"idNumberDTO", #"iso2DTO", #"short_nameDTO", #"calling_codeDTO" ]];
RKObjectMapping* contactsRequestMapping = [RKObjectMapping requestMapping];
[contactsRequestMapping addAttributeMappingsFromArray:#[ #"fullName", #"phoneNumber"]];
RKObjectMapping* userRequestMapping = [RKObjectMapping requestMapping];
[userRequestMapping addAttributeMappingsFromArray:#[ #"displayName", #"phoneNumber", #"status", #"userID", #"realName" ]];
[userRequestMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"deviceInfo" toKeyPath:#"device" withMapping:deviceRequestMapping]];
[userRequestMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"memberShipDetails" toKeyPath:#"memberShip" withMapping:msRequestMapping]];
[userRequestMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"country" toKeyPath:#"country" withMapping:countryRequestMapping]];
[userRequestMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"contacts" toKeyPath:#"contacts" withMapping:contactsRequestMapping]];
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:userRequestMapping objectClass:[UserDTO class] rootKeyPath:nil];
//Create Objects
UserDTO *user = [[UserDTO alloc]init];
..........
DeviceDTO *device = [[DeviceDTO alloc]init];
..........
user.deviceInfo = device;
MemberShipDTO *ms = [[MemberShipDTO alloc]init];
.......
user.memberShipDetails = ms;
RKObjectMapping *errorMapping = [RKObjectMapping mappingForClass:[ErrorMapping class]];
[errorMapping addAttributeMappingsFromArray:#[ #"errorCode", #"errorMessage" ]];
RKObjectMapping* deviceRespMapping = [RKObjectMapping mappingForClass:[DeviceDTO class]];
[deviceRespMapping addAttributeMappingsFromArray:#[ #"model", #"name", #"systemName", #"systemVersion", #"devToken" ]];
RKObjectMapping* msRespMapping = [RKObjectMapping mappingForClass:[MemberShipDTO class]];
[msRespMapping addAttributeMappingsFromArray:#[ #"validSince", #"validTill" ]];
RKObjectMapping* contactsRespMapping = [RKObjectMapping mappingForClass:[ContactDTO class]];
[contactsRespMapping addAttributeMappingsFromArray:#[ #"fullName", #"phoneNumber"]];
RKObjectMapping* userRespMapping = [RKObjectMapping mappingForClass:[UserDTO class]];
[userRespMapping addAttributeMappingsFromArray:#[ #"displayName", #"phoneNumber", #"status", #"userID", #"realName" ]];
[userRespMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"deviceInfo" toKeyPath:#"device" withMapping:deviceRespMapping]];
[userRespMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"memberShipDetails" toKeyPath:#"memberShip" withMapping:msRespMapping]];
[userRespMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"country" toKeyPath:#"country" withMapping:countryRequestMapping]];
[userRespMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"contacts" toKeyPath:#"contacts" withMapping:contactsRespMapping]];
[errorMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"user" toKeyPath:#"user" withMapping:userRespMapping]];
RKResponseDescriptor *errorDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:errorMapping pathPattern:nil keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[RKMIMETypeSerialization registerClass:[RKNSJSONSerialization class] forMIMEType:#"application/json"];
[[RKObjectManager sharedManager] setRequestSerializationMIMEType:RKMIMETypeJSON];
[[RKObjectManager sharedManager] setAcceptHeaderWithMIMEType:RKMIMETypeJSON];
[[RKObjectManager sharedManager] addRequestDescriptor:requestDescriptor];
[[RKObjectManager sharedManager] addResponseDescriptor:errorDescriptor];
[[RKObjectManager sharedManager] postObject:user path:#"user/integrate" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult){
NSArray* statuses = [mappingResult array];
NSLog(#"Loaded statuses: %#", statuses);
errorCode = [statuses objectAtIndex:0];
NSLog(#"errorCode == %#", errorCode);
block(errorCode);
RKLogInfo(#"Load collection of Articles: %#", mappingResult.array);
}failure:^(RKObjectRequestOperation *operation, NSError *error) {
block(nil);
RKLogError(#"Operation failed with error: %#", error);
}];
}
My JSON on my request is fine & the response is also fine:
{
"errorMessage": null,
"errorCode": 190,
"user": {
"displayName": "Saif",
"phoneNumber": "+xxx",
"userID": "xxx",
"country": {
"idNumberDTO": 83,
"short_nameDTO": "Germany",
"calling_codeDTO": "+49",
"iso2DTO": "DE"
},
"device": {
"devToken": "xxx",
"model": "iPhone",
"name": "Saifs iPhone",
"systemName": "iPhone OS",
"systemVersion": "6.1.4",
"id": null
},
"memberShip": {
"validSince": 1376047810000,
"validTill": 1407583810000,
"id": null
},
"contacts": [
{
"fullName": "xxx",
"phoneNumber": "xxx"
},
{
"fullName": "xxx",
"phoneNumber": "xxx"
},
....,
....,
....
],
"id": null
}
}
On my RK methode i get on this line:
errorCode = [statuses objectAtIndex:0];
this Error:
2013-08-09 13:30:12.246 xxx![19310:440f] W restkit.object_mapping:RKMapperOperation.m:99 Adding mapping error: Expected an object mapping for class of type 'UserDTO', provider returned one for 'ErrorMapping'
2013-08-09 13:30:12.247 xxx![19310:440f] I restkit.network:RKObjectRequestOperation.m:250 POST 'http://192.168.2.115:8080/WAZZUUPWS/rest/service/user/integrate' (200 OK / 0 objects) [request=1.3406s mapping=0.0047s total=1.3461s]
2013-08-09 13:30:12.249 xxx![19310:907] Loaded statuses: (
)
2013-08-09 13:30:12.250 xxx![19310:907] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 0 beyond bounds for empty array'
It looks like the response couldnt be mapped and thats why the Resultarray is empty. Any idea how i can map a complex object from a response?
From the Restkit documentation:
By default when postObject or putObject are used, RestKit is automatically going to try to map the JSON result into the sourceObject that was posted.
In your code, you are posting an UserDTO instance and receiving a JSON response that should map to ErrorMapping. Hence, you are getting RKMappingErrorTypeMismatch from Restkit.
As explained in the documentation, instead of using post, you can create an RKRequestOperation yourself and nil the target object to override the defaults.
RKObjectRequestOperation *operation = [[RKObjectManager sharedManager] appropriateObjectRequestOperationWithObject:user
method:RKRequestMethodPOST
path:#"user/integrate"
parameters:nil];
operation.targetObject = nil;
[operation setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
DLog(#"integrated user: %#", mappingResult.firstObject);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
DLog(#"error integrating user: %#", user);
}];
However, if you can modify the backend, I'd prefer to change the returned JSON. Instead of including the error codes in the JSON response, use relevant (HTTP) response status codes and include only domain model specific entities in your responses.
Here is my setup, Post a list of objects in Json to server and got back with updated results that will be mapped to core data and updated.
-- Post Boday --
{
"memberId": "1000000",
"contacts": [
{
"phoneNumber": "+12233333333",
"firstName": "john",
"lastName": "H"
},
{
"phoneNumber": "+12244444444",
"firstName": "mary",
"lastName": "K"
}
]
}
-- Post Response --
{
"contacts": [
{
"phoneNumber": "+12233333333",
"firstName": "john",
"lastName": "k",
"isMember": "yes"
},
{
"phoneNumber": "+12244444444",
"firstName": "mary",
"lastName": "k",
"isMember": "no"
}
]
}
I found another thread which discusses very similar to my case. RestKit: How does one post an array of objects?
This is my setup.
-- SHContact.h --
#interface SHContact : NSManagedObject {}
#property (nonatomic, strong) NSString* phoneNumber;
#property (nonatomic, strong) NSString* firstName;
#property (nonatomic, strong) NSString* lastName;
#property (nonatomic, strong) NSNumber* isMember;
#end
-- SHContactPost.m --
#import "SHContactPost.h"
#implementation SHContactPost
#synthesize contacts;
#synthesize memberId;
- (NSArray*)contacts {
return <list-of-SHContact>;
}
- (NSString *)memberId {
return #"my-member-id";
}
#end
-- RK Mapping --
// Setup our object mappings for SHContact
RKManagedObjectMapping* contactMapping =
[RKManagedObjectMapping mappingForClass:[SHContact class]
inManagedObjectStore:objectManager.objectStore];
contactMapping.primaryKeyAttribute = #"phoneNumber";
[contactMapping mapAttributes:#"phoneNumber",
#"firstName", #"lastName", #"isMember", nil];
[objectManager.mappingProvider setObjectMapping:contactMapping
forKeyPath:#"contacts"];
[[objectManager mappingProvider]
setSerializationMapping:[contactMapping inverseMapping]
forClass:[SHContact class]];
RKObjectMapping *cPostMapping = [RKObjectMapping
mappingForClass:[SHContactPost class]];
[cPostMapping mapKeyPath:#"contacts"
toRelationship:#"contacts"
withMapping:contactMapping];
[cPostMapping mapAttributes:#"memberId", nil];
[objectManager.mappingProvider
setSerializationMapping:[cPostMapping inverseMapping]
forClass:[SHContactPost class]];
[objectManager.router routeClass:[SHContactPost class]
toResourcePath:#"/contacts"
forMethod:RKRequestMethodPOST];
objectManager.serializationMIMEType = RKMIMETypeJSON;
-- Post to server --
SHContactPost *post = [[SHContactPost alloc] init];
RKObjectManager* manager = [RKObjectManager sharedManager];
[manager postObject:post delegate:self];
Server post and response is SUCCESSFUL. However, Reskit was not able to map the result back to the list of SHContact objects. This is the exception. Am I missing something?
restkit.network:RKObjectLoader.m:222 Encountered errors during mapping:
Cannot map a collection of objects onto a non-mutable collection.
Unexpected destination object type 'SHContactPost'
UPDATE: This is what I changed to make it work for me.
-- RK Mapping --
// Setup our object mappings for SHContact
RKManagedObjectMapping* contactMapping =
[RKManagedObjectMapping mappingForClass:[SHContact class]
inManagedObjectStore:objectManager.objectStore];
contactMapping.primaryKeyAttribute = #"phoneNumber";
[contactMapping mapAttributes:#"phoneNumber",
#"firstName", #"lastName", #"isMember", nil];
[objectManager.mappingProvider addObjectMapping:contactMapping];
-- Post to server --
SHContactPost *post = [[SHContactPost alloc] init];
RKObjectManager* manager = [RKObjectManager sharedManager];
RKObjectMapping* responseMapping = [manager.mappingProvider
objectMappingForClass:[SHContact class]];
[manager postObject:post usingBlock:^(RKObjectLoader *loader) {
loader.delegate = self;
loader.targetObject = nil;
responseMapping.rootKeyPath = #"contacts";
loader.objectMapping = responseMapping;
}];
loader.targetObject needs to set it to nil, otherwise, it won't work.
I'm using RestKit for the first time, and its feature-set looks great. I've read the document multiple times now and I'm struggling to find a way to POST JSON params to a feed and map the JSON response. From searching on stackoverflow I found a way to send the JSON params via a GET, but my server only takes POST.
Here is the code I have so far:
RKObjectMapping *issueMapping = [RKObjectMapping mappingForClass:[CDIssue class]];
[objectMapping mapKeyPath:#"issue_id" toAttribute:#"issueId"];
[objectMapping mapKeyPath:#"title" toAttribute:#"issueTitle"];
[objectMapping mapKeyPath:#"description" toAttribute:#"issueDescription"];
RKObjectManager* manager = [RKObjectManager objectManagerWithBaseURL:#"http://restkit.org"];
RKManagedObjectStore* objectStore = [RKManagedObjectStore objectStoreWithStoreFilename:#"News.sqlite"];
objectManager.objectStore = objectStore;
NSDictionary params = [NSDictionary dictionaryWithObjectsAndKeys: #"myUsername", #"username", #"myPassword", #"password", nil];
NSURL *someURL = [objectManager.client URLForResourcePath:#"/feed/getIssues.json" queryParams:params];
[manager loadObjectsAtResourcePath:[someURL absoluteString] objectMapping:objectMapping delegate:self]
From the another stackoverflow thread (http://stackoverflow.com/questions/9102262/do-a-simple-json-post-using-restkit), I know how to do a simple POST request with the following code:
RKClient *myClient = [RKClient sharedClient];
NSMutableDictionary *rpcData = [[NSMutableDictionary alloc] init ];
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
//User and password params
[params setObject:password forKey:#"password"];
[params setObject:username forKey:#"email"];
//The server ask me for this format, so I set it here:
[rpcData setObject:#"2.0" forKey:#"jsonrpc"];
[rpcData setObject:#"authenticate" forKey:#"method"];
[rpcData setObject:#"" forKey:#"id"];
[rpcData setObject:params forKey:#"params"];
//Parsing rpcData to JSON!
id<RKParser> parser = [[RKParserRegistry sharedRegistry] parserForMIMEType:RKMIMETypeJSON];
NSError *error = nil;
NSString *json = [parser stringFromObject:rpcData error:&error];
//If no error we send the post, voila!
if (!error){
[[myClient post:#"/" params:[RKRequestSerialization serializationWithData:[json dataUsingEncoding:NSUTF8StringEncoding] MIMEType:RKMIMETypeJSON] delegate:self] send];
}
I was hoping someone would help me marry these two code snippets into a workable solution.
To post an object what I do is associate a path to an object. Then use the method postObject from RKObjectManager.
I asume that you have already configured RestKit so you have the base path set and defined the object mapping for your CDIssue as you have in the code that you already have. With that in mind try this code:
//We tell RestKit to asociate a path with our CDIssue class
RKObjectRouter *router = [[RKObjectRouter alloc] init];
[router routeClass:[CDIssue class] toResourcePath:#"/path/to/my/cdissue/" forMethod:RKRequestMethodPOST];
[RKObjectManager sharedManager].router = router;
//We get the mapping for the object that you want, in this case CDIssue assuming you already set that in another place
RKObjectMapping *mapping = [[RKObjectManager sharedManager].mappingProvider objectMappingForClass:[CDIssue class]];
//Post the object using the ObjectMapping with blocks
[[RKObjectManager sharedManager] postObject:myEntity usingBlock:^(RKObjectLoader *loader) {
loader.objectMapping = mapping;
loader.delegate = self;
loader.onDidLoadObject = ^(id object) {
NSLog(#"Got the object mapped");
//Be Happy and do some stuff here
};
loader.onDidFailWithError = ^(NSError * error){
NSLog(#"Error on request");
};
loader.onDidFailLoadWithError = ^(NSError * error){
NSLog(#"Error on load");
};
loader.onDidLoadResponse = ^(RKResponse *response) {
NSLog(#"Response did arrive");
if([response statusCode]>299){
//This is useful when you get an error. You can check what did the server returned
id parsedResponse = [KFHelper JSONObjectWithData:[response body]];
NSLog(#"%#",parsedResponse);
}
};
}];