RestKit Collection - The Proper Key Path? - ios

I have the following JSON coming from my server...
{"Devices": [
{
"uuid": "d9084134-d5f7-43a3-9613-7faa769a822a",
"label": "",
"location": "a13d45f4-5ce0-48e3-bc3f-4076bb007037",
"capability": 0
},
{
"uuid": "a4ee0d3f-4a6a-4c61-81bd-3dfa9ab19e85",
"label": "",
"location": "a13d45f4-5ce0-48e3-bc3f-4076bb007037",
"capability": 3
}
]}
I'm trying to map this properly but I'm struggling with it... I have the following mappings setup...
RKEntityMapping *entityMapping = [RKEntityMapping mappingForEntityForName:#"Device" inManagedObjectStore:managedObjectStore];
[entityMapping setIdentificationAttributes:[NSArray arrayWithObject:#"uuid"]];
[entityMapping addAttributeMappingsFromDictionary:#{
#"uuid": #"uuid",
#"label": #"label",
#"location": #"location",
#"capability": #"capability"
}];
// GET
RKRequestDescriptor *getRequestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:[entityMapping inverseMapping] objectClass:[Device class] rootKeyPath:#"Device" method:RKRequestMethodGET];
[[RKObjectManager sharedManager] addRequestDescriptor:getRequestDescriptor];
[[RKObjectManager sharedManager].router.routeSet addRoute:[RKRoute routeWithClass:[Device class] pathPattern:#"Devices" method:RKRequestMethodGET]];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:entityMapping method:RKRequestMethodGET pathPattern:#"Devices" keyPath:#"Devices" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[[RKObjectManager sharedManager] addResponseDescriptor:responseDescriptor];
I have my keyPath set to "devices" which I would've thought would work. But RestKit isn't understanding it.
I'm using the following to get the objects from my server...
[[RKObjectManager sharedManager] getObjectsAtPath:#"Devices" parameters:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:#"%d", clientID], #"clientId", #"topic", #"forTopic", nil] success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
...
}... // Abbreviated for brevity.
So I believe my path "Devices" is correct for the pathPattern of "Devices". Are the parameters messing it up? I've had parameters before and it's always worked without having to specify anything in the mapping.
I'm using RestKit 0.27.1 currently.
UPDATE - Fixed JSON
I fixed the JSON as mentioned by Mahendra GP. So this is now a proper array. The RestKit log is now showing the JSON coming through in the trace log. However it still can't identify which object it is (Device).
Snippet of the log...
...and targetObject: (null)
2018-03-09 02:44:05.603734-0700 MyTestApp[31576:6868006] D restkit.object_mapping:RKMapperOperation.m:440 Finished performing object mapping. Results: (null)
2018-03-09 02:44:05.605334-0700 MyTestApp[31576:6868006] E restkit.network:RKObjectRequestOperation.m:172 GET 'http://[MY_IP]:8080/Devices?clientId=5199&forTopic=topic' (200 OK / 0 objects) [request=2.6488s mapping=0.0000s total=2.6633s]: Error Domain=org.restkit.RestKit.ErrorDomain Code=1001 "No response descriptors match the response loaded."

I finally figured out my issue by fluke trial and error. I wasn't able to find any specific posts on SO or over on RestKit's site. Therefore I thought I would post the, simple, solution to the problem here.
I kept getting errors that none of my response descriptors matched any of the key paths. I thought it had to do with the parameters I was passing to the GET as it would show that in the error trying to compare it to just the Devices path that the response descriptor was setup with. However this turned out not to be the issue at all.
The issue was with my RKResponseDescriptor setup. I originally had...
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:entityMapping method:RKRequestMethodGET pathPattern:#"Devices" keyPath:#"Devices" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
The solution here is the "pathPattern". This needed to be set to nil.
I've been using RestKit for many many years. Prior to the 0.2x releases. I remembered something about RestKit being able to identify an entity and it's multiple just by pluralizing it. I finally thought that perhaps because I was specifying it in the pathPattern it was causing the issue. So once I eventually set that to nil it finally started working. I actually think the reason why was that setting it to nil was like setting it it to a wildcard for any pathPattern since my path actually had parameters tagged onto them.
However in another use case I had I did specify the pathPattern but instead of it being the plural of the mapped entity it was something different. So instead of Devices I had something like DeviceForUser. So there seems to be a problem when specifying the plural of the entity in the pathPattern. Especially if using parameters in the getObjectsAtPath call.
So for clarity, all of the code above is the same. It just needs this change...
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:entityMapping method:RKRequestMethodGET pathPattern:nil keyPath:#"Devices" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
I hope this helps anyone else who might run into this.

Related

Multiple objects are not getting saved cored data restkit

I am saving 4 different tokens on Server using Restkit Coredata.
using response descriptor.
RKResponseDescriptor *tokenDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:instagramToken method:RKRequestMethodGET pathPattern:#"register/token" keyPath:#"data" statusCodes:statusCodes];
// And I'm calling it this way.
[[RKObjectManager sharedManager] getObjectsAtPath:#"register/token" parameters:params success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
}];
it is storing very well and I can access it in coredata as well. but the problem is,
If I save 1 token on server and map the response all good no issue in that. But when I save other token on Server the new response get mapped in coredata and over-right the old response.
I want to keep all the responses in coredata.
Please help on this.
The server needs to return a unique identity for each token so you know which is which. You need an attribute in your model to store that and you need to add that to the mapping and set it as the mapping identification attribute. Finally you need to connect your RK managed object store with an object cache so that it can search for duplicates to update (instead of always creating new instances).

Object mapper for xml in RestKit

I am trying map error response from server xml. In version 0.21 of restkit it worked ok. In last version it did not (0.22 and above). Possible response from server is <authorization-fail/>. Many server functions can lead to this response.
+ (void)addAuthErrorMapping:(RKObjectManager*)objectManager
{
RKObjectMapping* errorMapping = [RKObjectMapping mappingForClass:[RKErrorMessage class]];
[errorMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:nil
toKeyPath:#"errorMessage"]];
RKResponseDescriptor* errorResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:errorMapping
method:RKRequestMethodGET
pathPattern:nil
keyPath:#"authorization-fail"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptor:errorResponseDescriptor];
}
In 0.21 version I am getting dictionary with authorisation-fail key. In last versions result is empty dictionary.
This should never have worked.
The server should be returning the response to you with a non-2xx HTTP response code, and you should be using that code to trigger your response descriptor.
As it is, you're triggering the response descriptor all the time and telling it to look for - and drill into - the authorisation-fail. So, if that key exists but is empty you should get an RKErrorMessage that's empty.
If you have some other response descriptor that matches the response before this one (i.e. any other with a nil path pattern and success response code) then this one will never work and that is why you're getting an empty dictionary.
So, the best option is to change the server response, and the workaround solution is to use a dynamic mapping which checks the content and provides either a good response or an error mapping to be used.

RestKit .20 RKRequestDescriptor postObject - Expected status code in (400-499), got 200

Having a bit of a strange problem, I'm using RestKit to post a CoreData object to a remote web service. Everything on the remote end looks to be working fine but it seems like RestKit is having some issues mapping the response.
My object response mappings look like this, I have a User class with a UserProfile one to one relationship:
//UserProfile
RKEntityMapping * userProfileMapping =
[RKEntityMapping mappingForEntityForName:NSStringFromClass([UserProfile class])
inManagedObjectStore:[manager managedObjectStore]];
//…mapping
//User
RKEntityMapping * userMapping =
[RKEntityMapping mappingForEntityForName:NSStringFromClass([User class])
inManagedObjectStore:[manager managedObjectStore]];
//…mapping
//relationship mapping
[userMapping addPropertyMapping:[RKRelationshipMapping
relationshipMappingFromKeyPath:#"userProfile"
toKeyPath:#"userProfile"
withMapping:userProfileMapping]];
//add the response mappings for GET success
[manager addResponseDescriptorsFromArray:#[
[RKResponseDescriptor responseDescriptorWithMapping:userMapping
method:RKRequestMethodGET
pathPattern:nil
keyPath:#"currentUser"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]] …rest of my mappings];
The request mapping is the inverse of userMapping
RKRequestDescriptor * currentUserRequestDescriptor =
[RKRequestDescriptor requestDescriptorWithMapping:[userMapping inverseMapping]
objectClass:[User class]
rootKeyPath:#"currentUser"
method:RKRequestMethodPOST];
[manager addRequestDescriptor:currentUserRequestDescriptor];
My GET requests on the User class are working fine, POST is also working correctly.
To update my User coreData object I'm using `postObject'
[objectManager postObject:updatedUser
path:#"update"
parameters:nil
success:…handler
failure:…handler]
The POST works correctly and the object is saved in my remote service.
However, my success handler is never called and I never get the updated User object. I have logging on and I see the valid JSON in the response.
response.body={"currentUser":{….user object}}
Instead, failure is called, it seems to want a 400 response instead of the 200 my service is responding with.
"NSLocalizedDescription" -> "Expected status code in (400-499), got 200"
I don't see a way to set the expected statusCodes on RKRequestDescriptor like on RKResponseDescriptor.
Thanks
Because your User response descriptor uses method:RKRequestMethodGET so it won't be considered as a match for the response from your POST request.
Instead, set the method to RKRequestMethodAny.
Presumably RestKit is expecting a 400-499 because you have a response descriptor configured to handle error responses at that path.

RKResponseDescriptor in RESTKit is Deprecated

I'm trying to do some RESTKit http requests, and when I use the RKResponseDescriptor line of code, it says 'responseDescriptorWithMapping:pathPattern:keyPath:statusCodes:' is deprecated.
Here is how I coded it:
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor
responseDescriptorWithMapping:mapping pathPattern:nil keyPath:nil
statusCodes:statusCodeSet];
What exactly is the deal here, and how can I fix it?
Restkit 0.20.3 introduced new feature that allows you to use a response descriptor with multiple requests methods
+ (instancetype)responseDescriptorWithMapping:(RKMapping *)mapping
method:(RKRequestMethod)method
pathPattern:(NSString *)pathPattern
keyPath:(NSString *)keyPath
statusCodes:(NSIndexSet *)statusCodes
So you can just switch to this new implementation.
I had to search a fair bit to figure out what to put for method, so I thought I would share the specifics for others:
RKResponseDescriptor *responseDescriptor =
[RKResponseDescriptor responseDescriptorWithMapping:mapping
method:RKRequestMethodAny
pathPattern:nil keyPath:nil
statusCodes:statusCodeSet];
I used the general RKRequestMethodAny, but you can use something more specific if you prefer.

RKRoute with "/" as parameter doesn't work

I'm trying to set up a RKRoute to get folder contents from the Dropbox API using RestKit.
The URL to get the contents is https://api.dropbox.com/1/metadata/dropbox/<path>.
So I set up the response and route like this:
// objectManagers baseURL is #"https://api.dropbox.com/1/"
RKResponseDescriptor *rootResponse = [RKResponseDescriptor responseDescriptorWithMapping:dynamicMapping pathPattern:#"metadata/dropbox:path" keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager.router.routeSet addRoute:[RKRoute routeWithClass:[DropboxFolder class] pathPattern:#"metadata/dropbox:path" method:RKRequestMethodGET]];
// dropboxFolder.path is #"/" for root
// dropboxFolder.path is #"/Images" for the "Images"-folder contained in root
But then matching of the path fails in [RKPathMather matchesPath:tokenizeQueryStrings:parsedArguments: because there the number of slashes are checked like this: RKNumberOfSlashesInString(self.patternString) == RKNumberOfSlashesInString(self.rootPath)
The mapping works, when I comment out this checking, but I'm sure it's needed in some other cases.
The correct approach is to use 2 different response descriptors and routes. The slashes are important to allow RestKit to properly differentiate between different URLs, paths and patterns. You can use the same mapping so it's 'just' another couple of lines of configuration.
I think I found out a cleaner solution for my problem:
I configured my response descriptor and route like this:
RKResponseDescriptor *rootResponse = [RKResponseDescriptor responseDescriptorWithMapping:dynamicMapping pathPattern:#"metadata/dropbox/:path" keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
RKRoute *route = [RKRoute routeWithClass:[DropboxFolder class] pathPattern:#"metadata/dropbox/:path" method:RKRequestMethodGET];
route.shouldEscapePath = YES;
[objectManager.router.routeSet addRoute:route];
Now it works with 1 response descriptor and 1 route. I don't know if this works with other APIs using paths but it does with Dropbox. (The URL for root is now https://api.dropbox.com/1/metadata/dropbox/%2F)

Resources