I just started learning RestKit. I am trying to use it to get nearby venues using Foursquare apis. but every time I try " objectLoader:(RKObjectLoader *)objectLoader didLoadObjects:(NSArray *)objects " I get this warning "WARNING: Failed mapping nested object: (null)".
I am really confused. Following is my mapping code which is in init method:
_objectManager = [RKObjectManager objectManagerWithBaseURL:#"https://api.foursquare.com"];
RKObjectMapping *locationMapping = [RKObjectMapping mappingForClass:[Location class]];
[locationMapping mapKeyPath:#"address" toAttribute:#"address"];
[locationMapping mapKeyPath:#"city" toAttribute:#"city"];
[locationMapping mapKeyPath:#"state" toAttribute:#"state"];
[_objectManager.mappingProvider setMapping:locationMapping forKeyPath:#"location"];
RKObjectMapping *venueMapping = [RKObjectMapping mappingForClass:[Groups class]];
[venueMapping mapRelationship:#"location" withMapping:locationMapping];
[_objectManager.mappingProvider setMapping:venueMapping forKeyPath:#"groups"];
RKObjectMapping *responseMapping = [RKObjectMapping mappingForClass:[Response class]];
[responseMapping mapRelationship:#"groups" withMapping:venueMapping];
[_objectManager.mappingProvider setMapping:responseMapping forKeyPath:#"response"];
return self;
Following is the code in "locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation "
NSString *resourceURLString = [NSString stringWithFormat:#"/v2/venues/search?ll=%f,%f&limit=10&client_id=LEA1MYN1Z1QWSBFIOA1T4FREVVNF0R1ADQKMA0AMGL3CN5N4&client_secret=M2W204MKEAO4KV5L4ZXUYWK2GC32PO0KT5PWYJQBP4FKBTMO",newLocation.coordinate.latitude, newLocation.coordinate.longitude];
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:resourceURLString delegate:self];
[_locationManager stopUpdatingLocation];
and finally this code is in "objectLoader:(RKObjectLoader *)objectLoader didLoadObjects:(NSArray *)objects"
_objects = [objects retain];
NSLog(#"object that i receive is %#",objects);
Following is the response i get in console:
2012-02-26 13:15:39.872 restKitPractice[8609:690b] W restkit.object_mapping:RKObjectMappingOperation.m:372 WARNING: Failed mapping nested object: (null)
2012-02-26 13:15:39.873 restKitPractice[8609:207] object that i receive is (
"Response: 0x6b5cb20"
)
You can check the Venues json from Foursquare using this link:
https://api.foursquare.com/v2/venues/search?ll=40.562362,-111.938689&limit=10&client_id=LEA1MYN1Z1QWSBFIOA1T4FREVVNF0R1ADQKMA0AMGL3CN5N4&client_secret=M2W204MKEAO4KV5L4ZXUYWK2GC32PO0KT5PWYJQBP4FKBTMO
Any help or suggestions would be appreciated as I am really struggling with this issue.
Thanks
Vik
You should take a look at the object mapping docs.
Your mapping definitions do not match the json you're trying to parse.
I realize this is an old question, but I've seen the same warning. The cause is that you have JSON like this {"response":{}} that you want to map to an object. Since there are no keys in the object, it essentially maps to nil. So, the warning is pretty harmless. You will not get a Response object back because the server essentially returned something that RestKit can't map because there are no keys in the JSON that match any of your attribute mappings. RestKit is letting you know that with the warning and falling back to not creating an object.
Related
I'm attempting to use RestKit to handle posting an updated User object to my remote web service.
Currently my GET requests seem to be working fine but I'm having issues using
[[RKObjectManager sharedManager] postObject:updatedUser path:#"path" parameters:nil success:nil failure:nil];
Invoking this method is throwing a EXC_BAD_ACCESS exception.
My mappings are set up as follows, I believe I have both the RKRequestDescriptor and RKResponseDescriptor's.
User Response Mapping:
RKEntityMapping * userMapping =
[RKEntityMapping mappingForEntityForName:NSStringFromClass([User class])
inManagedObjectStore:[manager managedObjectStore]];
….Setup mapping (I excluded a relationship mapping on this object)
[manager addResponseDescriptorsFromArray:#[
[RKResponseDescriptor responseDescriptorWithMapping:userMapping
method:RKRequestMethodGET
pathPattern:nil
keyPath:#"currentUser"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]]]
Request mapping:
[manager addRequestDescriptorsFromArray:#[
[RKRequestDescriptor requestDescriptorWithMapping:[userMapping inverseMapping]
objectClass:[User class]
rootKeyPath:nil
method:RKRequestMethodPOST]]];
The mappings seem to set up fine, the EXC_BAD_ACCESS exception is thrown when I call postObject
The test method looks like this, _updatedUser is a CoreData object fetched using [RKObjectManager sharedManager] getObjectsAtPath:…
-(void) doPost{
//this user is a CoreData object fetched using
[_updatedUser setBio:#"This is an update!"];
RKObjectManager * objectManager = [RKObjectManager sharedManager];
[objectManager postObject:_updatedUser
path:#"update/user"
parameters:nil
success:…
failure:…];
}
I've attempted using NSZombies to find the cause of this but I have't had much luck.
From what I can tell the start of the issue seems to be coming from RKObjectParameterization's -[RKObjectParameterization mappingOperation:didSetValue:forKeyPath:usingMapping:] where it looks like everything passed into the method is nil or an empty string.
Thanks!
Much thanks to Wain, after spending way too much time on this the error became instantly apparent after I turned on logging:
RKLogConfigureByName("RestKit", RKLogLevelWarning);
RKLogConfigureByName("RestKit/ObjectMapping", RKLogLevelTrace);
RKLogConfigureByName("RestKit/Network", RKLogLevelTrace);
It turns out I had a circular reference between mapped objects.
I have a one to one relationship where a User contains a UserProfile
I incorrectly set up a bidirectional relationship mapping between User and UserProfile
[userProfileMapping addPropertyMapping:[RKRelationshipMapping
relationshipMappingFromKeyPath:#"user"
toKeyPath:#"user"
withMapping:userMapping]];
[userMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"userProfile"
toKeyPath:#"userProfile"
withMapping:userProfileMapping]];
It looks like my endless loop was caused by userProfileMapping
Thanks Wain, logging lesson learned.
Um beginner with RestKit, first example for me was on foursquare API's and I've used RestKit with Blocks not delegates.
I want to retrive the name's of venues,this is the JSON response
and this is my code:
// App Delegate.m
RKObjectManager *objectManager = [RKObjectManager objectManagerWithBaseURLString:#"https://api.Foursquare.com/v2"];
RKManagedObjectStore *objectStore = [RKManagedObjectStore objectStoreWithStoreFilename:#"Venue.sqlite"];
objectManager.objectStore = objectStore;
objectManager.serializationMIMEType = RKMIMETypeJSON;
RKManagedObjectMapping *venueMapping = [RKManagedObjectMapping mappingForClass:[Venue class] inManagedObjectStore:objectStore];
[venueMapping mapKeyPath:#"id" toAttribute:#"id"];
[venueMapping mapKeyPath:#"name" toAttribute:#"name"];
venueMapping.primaryKeyAttribute = #"id";
[objectManager.mappingProvider setMapping:venueMapping forKeyPath:#"response.venue"];
then in myViewController.m
-(void)loadVenues{
// When caling loadObjectsAtResourcePath method it specify RKObjectLoader which is the actual request.
// within these block you can take more options to controll the request.
[[RKObjectManager sharedManager]loadObjectsAtResourcePath:#"/venues/40a55d80f964a52020f31ee3?oauth_token=FNQPN5P5EKLJ5IQ44TMWO00I3W033M0Y1TKINW2OTF2VIOTP&v=20130512" usingBlock:^(RKObjectLoader* loader)
{
loader.objectMapping = [[RKObjectManager sharedManager].mappingProvider objectMappingForClass:[Venue class]];
loader.onDidLoadObject = ^(NSArray *objects)
{
NSLog(#"onDidLoadObject Blocks");
self.data = objects;
[self.tableView reloadData];
};
}
];
}
and the app is entering the block of onDidLoadObject but every time the array is empty !!
even when I test the link on browser it comes with data.
When I debug the loader.URL it always come with these
https://api.Foursquare.com/v2/venues/40a55d80f964a52020f31ee3?v=20130512&oauth_token=FNQPN5P5EKLJ5IQ44TMWO00I3W033M0Y1TKINW2OTF2VIOTP -- https://api.Foursquare.com/v2 -- https://api.Foursquare.com/v2
I don't know why load.URL is wrong ?!
I think um calling the 4square API's with the wrong way, anyone can help ? :)
-Put Your mapping as a class method to be accessed from all application classes, and done only once.
-change the Attribute "id" as it is reserved in Objective-C.
-add this to the block
[loader setObjectMapping:[RestKitMapping yourMapping]];
-then add this with your mapping code
[objectManager.mappingProvider setMapping:venueMapping forKeyPath:#"response.venue"];
-And use Delegates instead of blocks
I've two concerns regarding the above code:
1- Apparently the above JSON response, lists just one Venue .. So KeyPath should be "response.venue" not "response.venues"
2- Where's the mapping for ID? .. which is the primary key that RestKit uses to insert into DB? You need to set the primary key mapping.
I'm just starting out with RestKit 0.20.0 and I'm having trouble creating a nicely formatted JSON request.
I get this (from rest kit logs):
request.body=title=A%20glorious%20walk%20in%20the%20woods&startDateTime=2013-01-13%2016%3A09%3A33%20%2B0000&endDateTime=2013-01-13%2016%3A09%3A43%20%2B0000&points[][longitude]=-122.0307725&points[][latitude]=37.3310798&points[][longitude]=-122.0307334&points[][latitude]=37.33154242&points[][longitude]=-122.03075743&points[][latitude]=37.33138305&points[][longitude]=-122.03075659&points[][latitude]=37.33131185&points[][longitude]=-122.03057969&points[][latitude]=37.33156519&points[][longitude]=-122.03075535&points[][latitude]=37.33144466&points[][longitude]=-122.03076342&points[][latitude]=37.33123666&points[][longitude]=-122.03074488&points[][latitude]=37.33149482&points[][longitude]=-122.03068145&points[][latitude]=37.33155419&points[][longitude]=-122.03062909&points[][latitude]=37.33156564&points[][longitude]=-122.03076853&points[][latitude]=37.33115792
when I want this (normal json object with curly brackets and an array for the points property):
{
title: "Something",
startDateTime: "dateinfo",
endDateTime: "moredateinfo",
points: [
{
latitude: "37.33131313",
longitude: "122.4325454"
},
{
latitude: "37.33131313",
longitude: "122.4325454"
}
]
}
I have two main objects: A DLWalk than contains an NSSet of DLPoint objects (They are CoreData objects, but at the moment I am ignoring that and just focusing on creating an HTTP Request)
Here's the code I'm using to create my request:
// Point mapping
RKObjectMapping *mappingPoint = [RKObjectMapping requestMapping];
[mappingPoint addAttributeMappingsFromArray:#[#"latitude", #"longitude"]];
RKRequestDescriptor *reqDescPoint = [RKRequestDescriptor requestDescriptorWithMapping:mappingPoint objectClass:[DLPoint class] rootKeyPath:nil];
// Walk mapping
RKObjectMapping *mappingWalk = [RKObjectMapping requestMapping];
[mappingWalk addAttributeMappingsFromArray:#[#"endDateTime", #"startDateTime", #"title"]];
RKRequestDescriptor *reqDescWalk = [RKRequestDescriptor requestDescriptorWithMapping:mappingWalk objectClass:[DLWalk class] rootKeyPath:nil];
// Define the relationship mapping
[mappingWalk addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"points" toKeyPath:#"points" withMapping:mappingPoint]];
RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:#"http://192.168.1.10:8080"]];
[manager addRequestDescriptor:reqDescWalk];
[manager addRequestDescriptor:reqDescPoint];
[manager addResponseDescriptor:responseDescriptor];
// POST to create
[manager postObject:walk path:#"/walk/save" parameters:nil success:nil failure:nil];
So the question is: why am I not getting a normal looking JSON object in my POST body?
What you get as request.body is URL-encoded, which is RESTKit default behaviour and usually works fine.
If you want it to be JSON-encoded, just insert this line before posting the query
manager.requestSerializationMIMEType=RKMIMETypeJSON;
For more information on this, have a look there in the API documentation for RKObjectManager class :
requestSerializationMIMEType
I'm using RestKit to pull data from the Foursquare API into my iphone app, but having trouble with the followup API call for a nested object.
Specifically:
I call the Venues search API (https://developer.foursquare.com/docs/venues/search) to retrieve a list of Venues. Each Venue has a unique ID which is included in the response. I do this with the following in my ViewController:
[objectManager loadObjectsAtResourcePath:[NSString stringWithFormat:#"%#?%#", [URL resourcePath], [URL query]] delegate:self];
I then do the RestKit mapping etc and store the Venues in a data array. Everything working fine up to here.
At this point I loop through the Venues in the data array, and have to make a followup API call to retrieve more details about each Venue. For each Venue, I use the unique ID and call the Venue detail API (https://developer.foursquare.com/docs/venues/venues). This is the part that stumps me. I am trying to get an NSArray of Photo objects returned from this second API call. So far I have tried variations of this:
for (id venue in self.data){
Venue *myvenue = (Venue *)venue;
RKURL *URL = [RKURL URLWithBaseURL:[objectManager baseURL] resourcePath:[NSString stringWithFormat:#"/venues/%#", myvenue.venueid] queryParameters:queryParams];
[objectManager loadObjectsAtResourcePath:[NSString stringWithFormat:#"%#?%#", [URL resourcePath], [URL query]] delegate:self];
}
and in my mapping:
RKObjectMapping *photosMapping = [RKObjectMapping mappingForClass:[Photo class]];
[photosMapping mapKeyPathsToAttributes:#"url", #"imageURL", nil];
[venueMapping mapRelationship:#"photos" withMapping:photosMapping];
[objectManager.mappingProvider setMapping:photosMapping forKeyPath:#"response.photos"]; // not sure if this keypath is for the second API call
and my Venue class has this:
#property (strong, nonatomic) NSArray *photos;
Venue.photos always returns empty. Any suggestions?
From the code you have posted, nowhere are you linking the response with a venue. I'm not sure on the exact JSON returned by foursquare but you will need to conceive a way to map using your venue mapping which has a nested photo object. If foursquare does not return some reference to the original Venue object in it's response, you can try setting the RKObjectLoader's targetObject property to the Venue (myVenue in your code)
Not sure if this is the 'best' way to do it, but I got it working in the end. Paul de Lange's comment about not mapping the venue and the photos correctly set me off on the correct path.
The foursquare response was not in the format that I required in my app, so I had to use RKObjectLoader's willMapData to modify the response slightly before the rest of the mapping operation.
Here it is in case it helps anybody in the future:
- (void)objectLoader:(RKObjectLoader *)loader willMapData:(inout id *)mappableData {
NSArray* origPhotosArray = [*mappableData valueForKeyPath:#"response.venue.photos.groups"];
NSMutableArray* newPhotosArray = [[NSMutableArray alloc] init];
// <snip> Some extra code goes here where I discarded photos I did not want.... </snip>
// Create copies of objects in the format that I want
NSMutableDictionary *myphoto = [[NSMutableDictionary alloc] initWithDictionary:Photo];
NSString *venueID = [*mappableData valueForKeyPath:#"response.venue.id"];
[myphoto setObject:venueID forKey:#"assignedVenueid"];
[newPhotosArray addObject:myphoto];
// Replace the new objects instead of the response objects
[*mappableData removeObjectForKey:#"response.venue.photos.groups"];
[*mappableData setObject:newPhotosArray forKey:#"response.venue.photos.groups"];
}
And in my mapping:
RKManagedObjectMapping *photosMapping = [RKManagedObjectMapping mappingForClass:[Photo class] inManagedObjectStore:objectStore];
[photosMapping mapKeyPath:#"url" toAttribute:#"imageURL"];
[photosMapping mapKeyPath:#"assignedVenueid" toAttribute:#"assignedVenueid"];
[venueMapping mapRelationship:#"photos" withMapping:photosMapping];
[objectManager.mappingProvider setMapping:photosMapping forKeyPath:#"response.venue.photos.groups"];
[photosMapping hasOne:#"assignedVenue" withMapping:venueMapping];
[photosMapping connectRelationship:#"assignedVenue" withObjectForPrimaryKeyAttribute:#"assignedVenueid"];
What method of RKObjectLoaderDelegate gets called after the method - (RKObjectLoader *)postObject:(id<NSObject>)object delegate:(id<RKObjectLoaderDelegate>)delegate is called?
I'm trying to run some more code once my POST is successful.
EDIT (more info):
Neither of the methods that #Paul mentions are being called, but my server is interpreting the POST somewhat correctly because the DB is being written to.
During app initialization, I am doing the following:
// Grab the reference to the router from the manager
RKObjectRouter *router = [RKObjectManager sharedManager].router;
// Define a default resource path for all unspecified HTTP verbs
[router routeClass:[EventMessage class] toResourcePath:#"/events_messages/:idPrimaryKey"];
[router routeClass:[EventMessage class] toResourcePath:#"/event_messages" forMethod:RKRequestMethodPOST];
And when I am ready to POST, I am doing this:
RKObjectMapping *objectMapping = [RKObjectMapping mappingForClass:[EventMessage class]];
[objectMapping mapKeyPath:#"user_id" toAttribute:#"userId"];
[objectMapping mapKeyPath:#"event_id" toAttribute:#"eventId"];
[objectMapping mapKeyPath:#"message" toAttribute:#"message"];
[[RKObjectManager sharedManager].mappingProvider registerMapping:objectMapping withRootKeyPath:#"event_message"];
[[RKObjectManager sharedManager] postObject:eventMessage delegate:self];
And this is the error that I get in the console:
Error Domain=JKErrorDomain Code=-1 "Unexpected token, wanted '{', '}', '[', ']', ',', ':', 'true', 'false', 'null', '"STRING"', 'NUMBER'." UserInfo=0x7b9f510 {JKAtIndexKey=0, JKLineNumberKey=1, NSLocalizedDescription=Unexpected token, wanted '{', '}', '[', ']', ',', ':', 'true', 'false', 'null', '"STRING"', 'NUMBER'.}
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects;
or
- (void)objectLoader:(RKObjectLoader*)objectLoader didFailWithError:(NSError*)error;
Once you have setup your mappings correctly the posting of objects is very easy. Like Paul Cezanne pointed out, the corresponding callbacks will be used.
For posting objects, however, you'll also need a serialisation mapping that tells the framework which parameters should be converted to POST parameters:
Mapping Example:
// Serialisation Mapping
RKObjectMapping *messageSendMapping = [RKObjectMapping mappingForClass:[NSMutableDictionary class]];
[messageSendMapping mapAttributes:#"name", #"number", #"text", nil];
[[RKObjectManager sharedManager].mappingProvider
setSerializationMapping:messageSendMapping
forClass:[Message class]];
Update: For trouble shooting purposes you might want to increase the log level for RestKit like so:
RKLogConfigureByName("RestKit", RKLogLevelTrace);
RKLogConfigureByName("RestKit/Network", RKLogLevelTrace);
RKLogConfigureByName("RestKit/ObjectMapping", RKLogLevelTrace);
RKLogConfigureByName("RestKit/CoreData", RKLogLevelTrace);
Make sure that RKObjectManager has been initialized:
NSString *url = #"http://example.com";
RKObjectManager* mgr = [RKObjectManager managerWithBaseURLString:url];
I wasted a bunch of time before noticing that sharedManager is not really a singleton, you have to initialize it.
After that you can use the sharedManager and get an reference to the first one created:
[[RKObjectManager sharedManager].mappingProvider setObjectMapping:mapping forResourcePathPattern:#"/RKKeyValueMappingExample"];