Restkit how to map arbitrary json (keys may change overtime) to NSDictionary - ios

Let's say that in response to a PUT request:
[[RKObjectManager sharedManager]
putObject:nil
path:#"/api/users/add-user"
parameters:dictionary
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult){
NSLog(#"Success");
}
failure:^(RKObjectRequestOperation *operation, NSError *error){
NSLog(#"Failure");
}
];
I get a "JSON" of unknown structure. I don't know what and how many keys to expect, but the values are all strings. How do I map it to a NSDictionary *object?

You can use a REST client to see the response and the structure of the JSON response, and create a corresponding model to map it against. Or if this is something that is not a consistent model, you can use NSJSONSerialization to parse the JSON into a KV pair, and your variable of interest in 'mappingResult'.
And you might want to look in AFNetworking 2.0. It works very well with your REST calls and parsing your JSON.
http://nshipster.com/afnetworking-2/

If the JSON response has an unknown structure then you either shouldn't be using RestKit (just use NSJSONSerialization and handle everything yourself) or you will need to use an RKDynamicMapping so that you can analyse the incoming data and decide what mapping to create (on-the-fly) and return to action the processing.

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).

Order of Parameters in a POST in RestKit

I have configured RestKit successfully so I can send out POST messages to the device I'm working with.
I have confirmed that when I use Chrome's Postman that the format of the message is correct.
I need to send......
id=87654321&content={"ts":1396215675,"payload":{"ssid_pass":"blah"}}&t=12345678
So in Chrome's postman, it goes out correct. But when I use RestKit postObject
[manager postObject:self path:#"/tom" parameters:#{#"id" : K_IDVALUE, #"content": myPayload, #"t":K_TVALUE, } success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"Successful Post!");
The order the device sees is wrong. It sees
content={"ts":1396215675,"payload":{"ssid_pass":"blah"}}&id=87654321&t=12345678
The device really wants the content to be between the id and t tags of the POST request. Is there a way to force RestKit to take the parameters as is? It appears that the message is being alphabetized (which I assume is happening at serialization).
Is there a way to force RestKit to take the parameters as is?
What you are passing into RestiKit:
#{#"id" : K_IDVALUE, … }
is a NSDictionary, which has no concept of ordering of its elements.
Your only chance then is that RestKit is using the mapping you specify to determine the order of the keys. I cannot check now into RestKit sources to confirm this hypothesis, but you could just try it out (or simply verify what is your mapping current ordering).

Dealing with unexpected response content types with AFNetworking / AFHTTPRequestOperation

I'm using AFNetworking to process HTTP requests in my iOS app. I have run into a stumbling block. I cannot be certain of what the response content type will be, but you have to set the response serializer BEFORE the request is processed. This means I could do an API request, expecting an image back, but actually there's some authentication error, so the server returns a JSON-formatted response instead.
Here's my code:
AFHTTPRequestOperation* op = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[op setResponseSerializer:[AFJSONResponseSerializer serializer]]; // ??????
[op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSDictionary* result = (NSDictionary*) responseObject;
onSuccess((NSArray*) result);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
onFailure(error);
}];
[op start];
As you can see, I've had to set the expected content type implicitly by setting the responseSerializer to [AFJSONResponseSerializer serializer]. So if I get something else back, it causes an error, even though I may still wish to parse an process that response when dealing with the error.
So my question is, should I just use the standard AFHTTPResponseSerializer, examine the response status code and then process the response body manually (as json, xml, html an image etc)?
Set the accepted content types you want on the serialiser with acceptableContentTypes:
AFJSONResponseSerializer *serializer = [AFJSONResponseSerializer serializer];
serializer.acceptableContentTypes = [NSSet setWithArray:#[#"text/plain", #"text/html"]];
[op setResponseSerializer:serializer];
From the docs:
By default, AFJSONSerializer accepts the following MIME types, which
includes the official standard, application/json, as well as other
commonly-used types:
application/json
text/json
You don't have to use AFJSONResponseSerializer, as you can create your own serializer as long as it conforms to the AFURLResponseSerialization protocol.
If you have JSON responses but XML error responses, you could just subclass AFHTTPResponseSerializer and do your own processing in there.
You could also use AFCompoundResponseSerializer to parse different response types just going through the serializers you give it.
Your API is a little unusual: if you aren't authorized, it should just use an HTTP 401 response, not JSON. But there's plenty of unusual API's out there and I bet you don't have control over this one.
The fix is straightforward:
Make an implementation of AFURLResponseSerialization that just acts as a proxy, and assign that one as the serializer for your request. When the response comes in, have it take a quick look at the data and then instantiate and call the right serializer.

Retrieving HTTPBody from RestKit response

I am using the postObject and putObject functions of restKit. I need to extract some info from the payload and put it in the header before sending it out.. any suggestions on how I can do that?
ex:
[[RKObjectManager sharedManager] postObject:object path:OBJECT parameters:nil success:success failure:failure];
I will probably have to do something like
// Create Payload for this request
// update header accordingly
// call postObject
I would like to avoid throwing away existing code..
I was able to do this by using all the params that I was passing before making the RK request.

why is RestKit changing my response content-type?

In short: I try to fetch data form the server with the content-type of the http request header set as #"text/html.. but for some reason RestKit changes that to application/JSON
Explanation:
If I were to make this request using just AFNetworking.. things work like a charm.. this is what my AFNetworking code looks like:
AFHTTPClient *client = [AFHTTPClient alloc] initWithBaseURL:
[NSURL URLWithString:kApiBaseUrl]];
singleton.parameterEncoding = AFJSONParameterEncoding;
[singleton setDefaultHeader:#"Accept" value:#"text/html"];
[client getPath:getPath parameters:nil success:successCallback failure:failureCallback];
If I use that exact same client and attach it to
MyClient *client = [MyClient getSingleton]; //MyClient is instantiated as above
self.objectManager = [[RKObjectManager alloc] initWithHTTPClient:client];
self.objectManager.managedObjectStore = self.managedObjectStore;
// this should have already been done by my client, but putting
// it here just to be sure
[self.objectManager setAcceptHeaderWithMIMEType:#"text/html"];
[[RKObjectManager sharedManager] getObjectsAtPath:kGradesPath
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
// handle success
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
// handle failure
}];
the error i get is:
restkit.network:RKObjectRequestOperation.m:576 Object request failed:
Underlying HTTP request operation failed with error: Error
Domain=org.restkit.RestKit.ErrorDomain Code=-1016 "Expected content type {(
"application/x-www-form-urlencoded",
"application/json"
)}, got text/html" UserInfo=0x8f7acd0
digging into the subject.. i put a break point in managedObjectRequestOperationWithRequest, then I checked the acceptableContentTypes of HTTPRequestOperation created, and it's nil! So i'm assuming that RestKit is just putting its own default acceptable content types.. i just don't know where and how to prevent it. ideas?
p.s. I don't have control over the server, so I can't change it's content-type header to application/JSON
Update:
It turns out that in RKObjectRequestOperation.m it gets the mime-type from [RKMIMETypeSerialization registeredMIMETypes];(line 354).. and so in RKMIMETypeSerialization.hthere is the method:
/**
Registers the given serialization class to handle content for the given MIME Type identifier.
MIME Types may be given as either a string or as a regular expression that matches the MIME Types for which the given serialization should handle. Serializations are searched in the reverse order of their registration. If a registration is made for an already registered MIME Type, the new registration will take precedence.
#param serializationClass The class conforming to the RKSerialization protocol to be registered as handling the given MIME Type.
#param MIMETypeStringOrRegularExpression A string or regular expression specifying the MIME Type(s) that given serialization implementation is to be registered as handling.
*/
+ (void)registerClass:(Class<RKSerialization>)serializationClass forMIMEType:(id)MIMETypeStringOrRegularExpression;
how do I use this to register a text/html content-type?
RestKit generally expects a single MIMEType (JSON) for its response data. However, you can tell it to handle other types like text/plain and text/html using the method you found and in my experience it's been pretty handy. Adding this to my RestKit config (which I do in my app delegate) allows me to accept both application/json and text/html as response data content-types.
[RKMIMETypeSerialization registerClass:[RKNSJSONSerialization class] forMIMEType:#"text/html"];
In my case, this is also helpful because Jersey - the web services framework the backend team at my company uses - defaults the content-type of empty payloads to text/plain, which triggers failure blocks unless I've specifically registered for that MIMEType.
Hope this helps.
I use change const value to type that recive from server api like this
NSString * const RKMIMETypeJSON = #"text/html";
if recived text structure same as JSON this approach works perfect

Resources