This is my response from API:
{"is_favorited":1}
I want to map it by RestKit but I can't get it working. This I have in AppDelegate.m:
RKObjectMapping *isFavoriteMapping = [RKObjectMapping mappingForClass:[IsFavorite class]];
[isFavoriteMapping addAttributeMappingsFromDictionary:#{
#"is_favorited" : #"Is_favorited",
}];
RKResponseDescriptor *isFavoriteResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:isFavoriteMapping
method:RKRequestMethodAny
pathPattern:#"/api/isPointFavorited/:sid"
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[manager addResponseDescriptor:isFavoriteResponseDescriptor];
Path is okay. I have token in address. I am calling it by:
RKObjectManager *manager = [RKObjectManager sharedManager];
[manager getObjectsAtPath: [NSString stringWithFormat:#"/api/isPointFavorited/%#", sid]
parameters:#{
#"pid":[NSString stringWithFormat:#"%#", actualPlace.Id]
}
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult)
{
...
}
failure:^(RKObjectRequestOperation *operation, NSError *error)
{
...
}];
I am using RestKit several times in app and with sid (login token) too. So there is no problem with address or method call. Problem must be in mapping but I don't know how to do it. I tried to change keyPath from nil to #"is_favorited" but it didn't help.
My Is_favorite.h:
#interface IsFavorite : NSObject
#property(nonatomic) NSNumber *Is_favorited;
#end
RestKit error:
Error Domain=org.restkit.RestKit.ErrorDomain Code=1001 "No mappable object representations were found at the key paths searched." UserInfo=0x1849cbc0 {DetailedErrors=(
), NSLocalizedFailureReason=The mapping operation was unable to find any nested object representations at the key paths searched: events, results
The representation inputted to the mapper was found to contain nested object representations at the following key paths: is_favorited
This likely indicates that you have misconfigured the key paths for your mappings., NSLocalizedDescription=No mappable object representations were found at the key paths searched., keyPath=null}
Edit - changes in AppDelegate but still not working:
RKObjectMapping *isFavoriteMapping = [RKObjectMapping mappingForClass:[IsFavorite class]];
[isFavoriteMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:nil toKeyPath:#"Is_favorited"]];
RKResponseDescriptor *isFavoriteResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:isFavoriteMapping
method:RKRequestMethodAny
pathPattern:#"/api/isPointFavorited/:sid"
keyPath:#"is_favorited"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[manager addResponseDescriptor:isFavoriteResponseDescriptor];
Remove you usage of addAttributeMappingsFromDictionary:, and change to:
[isFavoriteMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:nil toKeyPath:#"Is_favorited"]];
then change the keyPath on the response descriptor to #"is_favorited".
This is known as a nil key path mapping (because of the nil source key path in the mapping).
Related
I am using Restkit for a class that Extends NSManagedObject.
I am aware that rest kit itself has functionality to save to core data from network fetch. However, I cannot use that functionality due to the following reasons:
My application will be fetching data from sockets as well as from rest kit , so I would want a centralised location for saving/deleting logic.
My server does not confirm to rest protocols, so many times I have to send a POST request even when I really want to delete something in server.
So What I wanted to do was have my Model classes extend nsmanaged object, and save it when I want to. But I get this error:
CoreData: error: Failed to call designated initializer on
NSManagedObject class
Is there a way to go around this ?
I am fetching from server like this :
#implementation API_Login
+(void)performLoginWithEmail:(NSString*)email
withPassword:(NSString*)password
success:(void (^)(Token* user) )success
failure:failureblock failure{
RKObjectManager * objectManager = [APIHelper getRestObjectManager];
RKObjectMapping *tokenMapping = [RKObjectMapping mappingForClass:[Token class]];
//add mapping for token
[tokenMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:nil toKeyPath:#"token"]];
RKResponseDescriptor *responseDescriptor =
[RKResponseDescriptor responseDescriptorWithMapping:tokenMapping
method:RKRequestMethodGET
pathPattern:nil
keyPath:nil
statusCodes:[NSIndexSet indexSetWithIndex:200]];
[objectManager addResponseDescriptor:responseDescriptor];
// add mapping for error
RKObjectMapping *errorMapping = [RKObjectMapping mappingForClass:[Error class]];
[errorMapping addAttributeMappingsFromDictionary:#{#"message":#"message",#"badRequest":#"badRequest"}];
RKResponseDescriptor *errorResponseDescriptor =
[RKResponseDescriptor responseDescriptorWithMapping:errorMapping
method:RKRequestMethodGET
pathPattern:nil
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassClientError)];
[objectManager addResponseDescriptor:errorResponseDescriptor];
NSDictionary *queryParams = #{#"email" : email,
#"password" : password,
};
[objectManager postObject:nil
path:#"/users/api/login"
parameters:queryParams
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
Token* token = (Token*)[mappingResult firstObject] ;
// [AppDelegateHandle setToken:token];
success(token);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSArray* e = [[error userInfo] objectForKey:RKObjectMapperErrorObjectsKey];
Error *err = (Error*)[e objectAtIndex:0];
NSLog(#"%#",[err.badRequest allValues] );
failure(operation,error);
}];
}
#end
My Token class looks like:
#interface Token : NSManagedObject
#property NSString* token;
#end
and my api response looks like :
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjEsImlzcyI6Imh0dHA6XC9cL3JlbWluZGVyLmRldlwvdXNlcnNcL2FwaVwvbG9naW4iLCJpYXQiOjE0Njg1OTM3NjYsImV4cCI6MTQ2OTE5Mzc2NiwibmJmIjoxNDY4NTkzNzY2LCJqdGkiOiIxMDc3ZjBhY2ViYTFjOWZjZWNhYjkyMzYyOTA0ZmI4NSJ9.I6FHJLCCHr3EHQa8HgaDqxQMjF1HVyA5AymPjvBGDrM"
}
When I change Token to extend NSObject instead of NSManagedObject , everything works fine. What could the problem be ?
This happens because you're using RKObjectMapping instead of RKEntityMapping which is required if you're using a subclass of NSManagedObject.
You can't use a subclass of NSManagedObject if you aren't going to add it directly into a context.
If your request simply has a token then I wouldn't bother with RestKit probably, but in the general case I'd map to NSDictionary with the keys being the same as your managed object classes and then when you want to create your managed objects you can do so and 'import' the data to them with setValuesForKeysWithDictionary:.
Hello friends in stackoverflow:
I am having problem mapping data from the api using restkit.I really want to know where my mistake is.
Json format:
{
data: {
-current_condition: [1]
0: {
cloudcover: "16"
humidity: "59"
- weatherDesc: [1]
0: {
value: "Clear"
}
- weather: [5]
0: {
tempMinC: "10"
tempMinF: "50"
weatherCode: "119"
- weatherDesc: [1]
0: {
value: "Cloudy"
}
.......
}
here is my code how to do the mapping (I tried to mapping the 'cloudcover,humidity',and both 'weatherDesc' in current_condition and weather)
-(void)configureRestKit{
NSURL *baseURL = [NSURL URLWithString:#"http://www.raywenderlich.com"];
AFHTTPClient *client = [[AFHTTPClient alloc] initWithBaseURL:baseURL];
RKObjectManager *objectManager = [[RKObjectManager alloc] initWithHTTPClient:client];
RKObjectMapping *currentMapping = [RKObjectMapping mappingForClass:[CurrentCondition class]];
[currentMapping addAttributeMappingsFromArray:#[#"cloudcover",#"humidity",#"weatherDesc"]];
[currentMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"current_condition" toKeyPath:#"current_condition" withMapping:currentMapping]];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:currentMapping method:RKRequestMethodGET pathPattern:#"/demos/weather_sample/weather.php" keyPath:#"data.current_condition" statusCodes:[NSIndexSet indexSetWithIndex:200]];
[objectManager addResponseDescriptor:responseDescriptor];
//weahter Desc
RKObjectMapping *weatherMapping = [RKObjectMapping mappingForClass:[Weather class]];
[weatherMapping addAttributeMappingsFromDictionary:#{#"weatherDesc": #"myweatherDesc"}];
[weatherMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"weather" toKeyPath:#"weather" withMapping:weatherMapping]];
RKResponseDescriptor *weatherresponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:weatherMapping method:RKRequestMethodGET pathPattern:#"/demos/weather_sample/weather.php" keyPath:#"data.weather" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptor:weatherresponseDescriptor];
}
-(void)loadCurrentCondition{
NSDictionary *queryParams = #{#"format": #"json"};
[[RKObjectManager sharedManager] getObjectsAtPath:#"/demos/weather_sample/weather.php" parameters:queryParams success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
_myArr = mappingResult.array;
[self Humidity];
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"The error is :%#",error);
}];
}
-(void)Humidity{
restkitCurrentCondition *rkCC = [_myArr objectAtIndex:0];
NSLog(#"///////////////////////the humidity is: %ld",rkCC.humidity.longValue);
NSLog(#"//////////////////// the cloudcover is: %ld",rkCC.cloudcover.longValue);
NSLog(#"/////////////// the weatherDesc is %#",rkCC.weatherDesc[0][#"value"]);
NSLog(#"///////// the weatherDesc in weather is %#",rkCC.restkitweather.myweatherDesc[0][#"value"]);
NSLog(#"///////// the weatherDesc in weather is %#",rkCC.restkitweather.myweatherDesc);
}
here is what I get:
2014-07-16 14:21:36.076 myRestSample[3783:60b] I restkit:RKLog.m:33 RestKit logging initialized...
2014-07-16 14:21:36.154 myRestSample[3783:60b] I
restkit.network:RKObjectRequestOperation.m:150 GET
'http://www.raywenderlich.com/demos/weather_sample/weather.php?format=json'
2014-07-16 14:21:36.289 myRestSample[3783:3a0f] I
restkit.network:RKObjectRequestOperation.m:220 GET
'http://www.raywenderlich.com/demos/weather_sample/weather.php?format=json' (200 OK / 6
objects) [request=0.1323s mapping=0.0024s total=0.1525s]
2014-07-16 14:21:36.289 myRestSample[3783:60b] -[Weather humidity]: unrecognized
selector sent to instance 0x8f8eb30
I tried to change the descritor's keypath to 'nil',looks like this
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:currentMapping
method:RKRequestMethodGET
pathPattern:#"/demos/weather_sample/weather.php" keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
The Weather and CurrentCondition are NSObject and there are four properties in CurrentCondition(cloudcover,humidity, NSArray *weaterDesc,Weather *restkitweather).There are only two properties in Weather.h (NSArray *myweatherDesc,*weatherDesc)
It seems I have already got 6 objects but why I got '[Weather humidity] Unreconized'.
Can anyone help??please..
'[Weather humidity] Unreconized'
This means that you have a Weather Instance, but you are treating it as a current conditions instance.
As per my answer to your other question you should not be using _myArr = mappingResult.array; because you can't be sure what the array contains. Instead you should be using the dictionary and extracting the correct type of object for your needs based on the key path.
I'll try to explain how I want the mapping done:
TOP lvl json object contains Players object which is an array of Player objects
each Player object contains an array, I want each of those objects in the array to be of an Event object (custom object).
now since I have a mapping of the Player object and i'm getting the array filled, tho instead of Event objects(which is what I want), i'm getting NSDictionary objects. thing is that I do have a mapping of my Event class. my issue is getting the RestKit to map these into the array.
I've tried adding responseDescriptors of an Event class tho i've had no luck.
Here is the Player object mapping
RKObjectMapping* playerMapping = [RKObjectMapping mappingForClass:[Player class]];
[playerMapping addAttributeMappingsFromDictionary:#{
...more here
#"activeEvents" : #"activeEvents"
}];
here is the request method
NSURL *taskURL = [NSURL URLWithString:kAppWebApiURLPath];
// Set object manager with base url
RKObjectManager *objectManager = [RKObjectManager sharedManager];
objectManager = [RKObjectManager managerWithBaseURL:taskURL];
objectManager.requestSerializationMIMEType = RKMIMETypeJSON;
[objectManager.HTTPClient setDefaultHeader:#"Authorization" value:kAppWebAPIKey];
[objectManager.HTTPClient setDefaultHeader:#"Content-Type" value:#"application/json"];
RKRequestDescriptor * requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:[MappingProvider inverseLoginMapping] objectClass:[LoginInfo class] rootKeyPath:nil method:RKRequestMethodPOST];
[objectManager addRequestDescriptor:requestDescriptor];
RKResponseDescriptor *playersResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:[MappingProvider playerMapping] method:RKRequestMethodGET pathPattern:nil keyPath:#"players" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptor:playersResponseDescriptor];
NSLog(#"%#",loginInfo.iOSDeviceToken);
[objectManager postObject:loginInfo path:kAppWebApiLoginPath parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult)
{}...
Update, I now need one step further of mapping, My player object contains an array of events which I successfully mapped using
[playerMapping addRelationshipMappingWithSourceKeyPath:#"activeEvents" mapping:[MappingProvider eventMapping]];
yet now each of those Event objects contains an array of Players, so its like Players -> Events -> Players.
Here is the Mapping for both Event and Player objects :
RKObjectMapping* eventMapping = [RKObjectMapping mappingForClass:[Event class]];
[eventMapping addAttributeMappingsFromDictionary:#{
#"stuffhere" : #"stuffz"
}];
RKObjectMapping* playerMapping = [RKObjectMapping mappingForClass:[Player class]];
[playerMapping addAttributeMappingsFromDictionary:#{
#"name": #"name",
#"activeEvents" : #"activeEvents"
}];
[eventMapping addRelationshipMappingWithSourceKeyPath:#"activeEvents/players" mapping:playerMapping];
now I don't get a recursive function, but how do I state in code to make that relationship
mapping of the json array to assign to my local array property ?
Remove #"activeEvents" : #"activeEvents" from the mapping and replace it with:
[playerMapping addRelationshipMappingWithSourceKeyPath:#"activeEvents" mapping:eventMapping];
You should also only have one response descriptor because the data is nested.
So the problem is that when I'm trying to load entity from here I'm not getting things to work. My Pathpatterns seems to be wrong.
Here's my Mapping and Descriptor:
RKEntityMapping *statsMapping = [RKEntityMapping mappingForEntityForName:#"Stat" inManagedObjectStore:managedObjectStore];
[statsMapping addAttributeMappingsFromDictionary:#{
#"sort_id" : #"sortID",
#"id" : #"statID",
#"deleted" : #"deletedFlag",
#"created_at": #"createdAt",
#"updated_at": #"updatedAt"
}];
statsMapping.identificationAttributes = #[ #"statID" ];
[statsMapping addAttributeMappingsFromArray:#[ #"title"]];
RKEntityMapping *featuresMapping = [RKEntityMapping mappingForEntityForName:#"Feature" inManagedObjectStore:managedObjectStore];
[featuresMapping addAttributeMappingsFromDictionary:#{
#"sort_id" : #"sortID",
#"id" : #"featureID",
#"deleted" : #"deletedFlag",
#"created_at": #"createdAt",
#"updated_at": #"updatedAt",
}];
featuresMapping.identificationAttributes = #[ #"featureID" ];
[featuresMapping addAttributeMappingsFromArray:#[ #"title", #"value"]];
[statsMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"features" toKeyPath:#"features" withMapping:featuresMapping]];
RKResponseDescriptor *statsDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:statsMapping
pathPattern: #"/api/cars/:carID/features.json"
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptorsFromArray:#[newsDescriptor, catalogDescriptor, statsDescriptor]];
So when I use pathPattern:nil it works, but if no answer is returned by url it just tries to put another responsedescriptor to the response and gives me random data :)
The question is, if I have the car ID in the middle of the pattern, how should I declare it?
Thank you!
Edit1: This is how I do request:
- (void)getStats:(NSNumber *)carID
{
[[RKObjectManager sharedManager] getObjectsAtPath:[NSString stringWithFormat:#"api/cars/%#/features.json", carID]
parameters:#{#"auth_token" : [Constants authToken]}
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
RKLogInfo(#"Load complete: Stats loaded");
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
RKLogError(#"Load failed with error: %#", error);
[self showError:error withDelay:3];
}];
}
From your trace log there is some problem with your managed objects, not your mappings. Your entities are obviously defined in the model and with the correct names and with the appropriate attributes. So it looks like either you created classes for the entities but wrongly or that the object manager isn't being provided with an / the correct object store reference.
Your log contains CoreData: error: Failed to call designated initializer on NSManagedObject class 'Stat' and [<Stat 0xa68b7c0> valueForUndefinedKey:]: the entity (null) is not key value coding-compliant for the key "statID". which are both issues to do with the entity creation / referencing. It isn't clear how that happens based on the code posted.
Try changing your path pattern to remove the leading slash:
#"api/cars/:carID/features.json"
when defining your statsDescriptor as that can cause the pattern to not match.
I am writing an iOS app that interacts with a service to update a user's profile. In order to update a user's name, the iOS app PUTs a request with a body of the following format:
{"name" : {"first" : "FIRSTNAMEGOESHERE", "lastName":"LASTNAMEGOESHERE"}}
In the case where the request body is valid (i.e. both a first and last name are provided), the service returns a 204 (No Content) status code and an empty response body.
In the case where the request body is invalid (e.g. first name is missing, etc.), the service will return a status code of 400 and a response body in the following format:
{"code":"ERRORCODEHERE"}
The iOS app is using RestKit and I have been unable to figure out how to get it to both properly handle the success and failure case. If I use this:
- (void)updateUserName
{
RKObjectManager *objectManager = [RKObjectManager managerWithBaseURL:[[NSURL alloc] initWithString:#"http://someqaserver:8080"]];
[objectManager setRequestSerializationMIMEType:RKMIMETypeJSON];
RKObjectMapping *userMapping = [RKObjectMapping requestMapping];
[userMapping addAttributeMappingsFromDictionary:#{#"firstName" : #"first", #"lastName" : #"last"}];
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:userMapping objectClass:[OurUser class] rootKeyPath:#"name"];
[objectManager addRequestDescriptor:requestDescriptor];
[objectManager putObject:[OurUser getCurrentUser]
path:#"/mobile/profile/name"
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *result)
{
NSLog(#"Success!");
}
failure:^(RKObjectRequestOperation *operation, NSError *error)
{
NSLog(#"Fail!");
}];
}
This obviously does not work for the error case, but it does successfully execute the success block in the case where a 204 is returned from the server.
If I add the following code before calling putObject, the error handling works, but now the valid 204 case is deemed a failure:
RKObjectMapping *errorMapping = [RKObjectMapping mappingForClass:[ServiceError class]];
[errorMapping addAttributeMappingsFromDictionary:#{#"code" : #"errorCode"}];
RKResponseDescriptor *errorDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:errorMapping pathPattern:nil keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassClientError)];
[objectManager addResponseDescriptor:errorDescriptor];
This seems to happen since the accepted HTTP status codes are now set to those of the response descriptors provided and 204 is not one of them (or maybe it's something else?)
How can I get it so that:
1.) if a 204 is returned, it's seen as a success
2.) if a 4XX is returned, it's seen as a failure and the error code is properly mapped to my ServiceError object?
Thanks in advance for any help you can provide.
The problem was solved by also adding a response descriptor to handle the 204:
RKResponseDescriptor *successResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:[RKObjectMapping mappingForClass:nil]
method:RKRequestMethodPUT
pathPattern:nil
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[manager addResponseDescriptor:successResponseDescriptor];
code 204 is not an error, any 2xx status is deemed a success. 204 means 'no content', which is a perfectly valid response.
If you expect restkit to treat it as an error, make sure you throw a 3xx, 4xx or a 5xx error. In your case you are using a RKStatusCodeClassClientErrorm which would suggest you should throw a 4xx error.
See wikipedia for a complete list of error codes