I'm using RestKit into my iPhone application to load a list of countries. The problem is the elementToPropertyMappings method uses a dictionary to map each object. In my case I have an array of strings that I'd like to map to the name property on my Country class.
Anyone know how todo this?
elementToPropertyMappings
Must return a dictionary containing
mapping from JSON element names to
property accessors
(NSDictionary *)elementToPropertyMappings Declared In RKObjectMappable.h
My JSON Data
["Argentina","Australia","Austria","Belgium","Bolivia","Brazil","Bulgaria","Canada","Cayman Islands","China","Costa Rica","Croatia","Czech Republic","Denmark","Ecuador","Ethiopia","F.Y.R.O. Macedonia","Finland","France","French Polynesia","Germany","Guam","Hong Kong SAR","Indonesia","Ireland","Israel","Italy","Japan","Latvia","Lithuania","Luxembourg","Malaysia","Malta","Mexico","Morocco","Netherlands","New Zealand","Nicaragua","Norway","Papua New Guinea","Peru","Poland","Portugal","Puerto Rico","Qatar","Romania","Russia","Singapore","Slovakia","Slovenia","South Africa","South Korea","Spain","Sweden","Switzerland","Taiwan","United Arab Emirates","United Kingdom","United States","Venezuela","Vietnam"]
UPDATE:
I figured out how to use the RKClient to make a request so the Mapping functionality is skipped. Now I need to figure out what class to use for JSON parsing. The yajl-objc parser looks great but I don't want to include another parser if it can be done with a lib from RestKit.
-(void)loadLocations
{
NSLog(#"loadLocations");
RKObjectManager *objectManager = [RKObjectManager sharedManager];
[[RKClient sharedClient] get:#"/locations/countries.json" delegate:self];
}
- (void)request:(RKRequest*)request didLoadResponse:(RKResponse*)response {
NSLog(#"Loaded payload: %#", [response bodyAsString]);
// HOW CAN I PARSE THIS STRING INTO AN NSArray?
}
Figuring out the proper import for RKJSONParser was the most challenging thing for me.
If there is another way to accomplish this with the Mapping classes please let me know.
Here is the code involved with loading a simple array.
#import <RestKit/Support/RKJSONParser.h>
#implementation CountriesViewController
#synthesize countries;
-(void)loadLocations
{
NSLog(#"loadLocations");
[[RKClient sharedClient] get:#"/locations/countries.json" delegate:self];
}
- (void)request:(RKRequest*)request didLoadResponse:(RKResponse*)response {
NSLog(#"Loaded payload: %#", [response bodyAsString]);
RKJSONParser* parser = [RKJSONParser new];
countries = [parser objectFromString:[response bodyAsString]];
}
Support for array of strings was added on v0.10: Source
Related
I need to configure RestKit to interact with a server API that expects the following:
All requests are in multipart/form-data
There is a JSON payload. However the client must collapse the payload
into a form-data string and prepend it with "json="
Authentication is done via hashing the contents of JSON payload with
the URL including any GET parameters. And appending this hash to URL (Yes I know this is bad. But I
have no control over the API)
I'm trying to get RestKit append its serialization output to a json=, then modify the RKObjectLoader to computer and append the correct signature hash.
I've tried subclassing RKObjectManager and overriding sendObject:toResourcePath:usingBlock: to override the HTTPBody; but can't get this to work. I've also tried subclassing RKClient's and configureRequest: but it seems like this method gets called before object serialization.
This is my first day working with RestKit. Help would be much appreciated! Thanks!
Update: I've worked out a different solution than my original described bottom. I've created a subclasses of RKObjectLoader and RKObjectManager. The custom object manager simply has one change to return the custom object loader. The object loader's send message was overridden to perform custom construction.
Here's a solution I've worked out.
I use a subclass of RKClient where I override
- (void)configureRequest:(RKRequest *)request {
[super configureRequest:request];
request.delegate = self;
}
And do all my encoding/signing in the delegate.
- (void)requestWillPrepareForSend:(RKRequest *)request {
[self encodeJSONPayloadForRequest:request];
[self signRequest:request];
}
- (void)encodeJSONPayloadForRequest:(RKRequest*)request {
NSDictionary * bodyDict = (NSDictionary*)request.params;
NSError * error = nil;
NSString * jsonString = [bodyDict JSONStringWithOptions:JKSerializeOptionNone error:&error];
if (error) NSAssert(false, #"error serializing into JSON");
NSDictionary * dictionary = [NSDictionary dictionaryWithObject:jsonString forKey:#"json"];
request.params = dictionary;
}
I'll need to implement a mechanism for passing through to any pre-existing delegate. However, this approach seems to do the trick.
I've been using RestKit 0.10.0 for a while now and up until this point, I only posted serialized objects to my server:
[[RKObjectManager sharedManager] postObject:serializedObject
usingBlock:^(RKObjectLoader *loader) {
loader.delegate = self;
loader.objectMapping = responseMapping;
loader.serializationMIMEType = RKMIMETypeFormURLEncoded;
loader.targetObject = nil;
}];
So far, so good. But I now need to make a GET request to the server with a few query parameters. The first natural thing that came in mind was to do the same as I did for posting objects:
create a serialization mapping for the object encapsulating the query parameters
create a response mapping for the object being received from the server
define and use a router for RKRequestMethodGET (instead of RKRequestMethodPOST)
make the request using getObject:usingBlock (instead of postObject:usingBlock)
I soon found out this is not the way to do it, so after searching the available resources (RestKit Wiki, RestKit Google group) I now know of two solutions considered as valid:
Appending the query parameters to the resource path.
This works perfectly.
NSDictionary *queryParams = [NSDictionary dictionaryWithObjectsAndKeys:
token, #"accessToken",
[NSNumber numberWithInt:level], #"level",
[NSNumber numberWithInt:count], #"count",
nil];
NSString* resourcePath = [PEER_SUGGESTIONS_CONTROLLER_PATH stringByAppendingQueryParameters:queryParams];
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:resourcePath
usingBlock:^(RKObjectLoader *loader) {
loader.delegate = self;
loader.objectMapping = responseMapping;
}];
Setting the query parameters in the loader block.
This does not send the query parameters.
RKParams *params = [RKParams params];
[params setValue:token forParam:#"accessToken"];
[params setValue:[NSNumber numberWithInt:level] forParam:#"level"];
[params setValue:[NSNumber numberWithInt:count] forParam:#"count"];
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:PEER_SUGGESTIONS_CONTROLLER_PATH
usingBlock:^(RKObjectLoader *loader) {
loader.delegate = self;
loader.objectMapping = responseMapping;
loader.params = params;
}];
My questions are:
Why doesn't the second solution work?
Why is the first solution working without having to set the loader.targetObject to nil, although I do not have any root key path in the JSON response?
What are the cases where I should use the getObject:usingBlock method? What is its intended purpose?
What should I use loader.params for? The object mapping tutorial from the wiki says this property can be used to encapsulate POST parameters, but I do not see the point since I can wrap the parameters in the serialized object that is being sent with the method postObject:usingBlock.
Thanks.
[LATER EDIT]
Regarding the answer to my second question: I've been setting the targetObject to nil in the loader block when making POST requests beacause otherwise RestKit will try use the send object mapping for the response (check this link for a related discussion). But since I am using loadObjectsAtResourcePath:usingBlock:, there is no object being sent, therefore the response will naturally map on the response mapping without having to the set targetObject to nil.
Why doesn't the second solution work?
params is used to create a HTTP body, which is not used in a GET/HEAD request.
Why is the first solution working without having to set the loader.targetObject to nil, although I do not have any root key path
in the JSON response?
I think targetObject is nil by default. You normally don't set it, the request will create it if needed. The only time I use it is when requesting objects without primary keys or other weird problems.
What are the cases where I should use the getObject:usingBlock method? What is its intended purpose?
This is a convenience method so you don't have to remember all the correct syntax. Internally it just sends an object load request using GET.
EDIT:
Use this if you have an object you want to update.
What should I use loader.params for? The object mapping tutorial from the wiki says this property can be used to encapsulate POST
parameters, but I do not see the point since I can wrap the parameters
in the serialized object that is being sent with the method
postObject:usingBlock.
Whatever you put in params will be serialized to an HTTP body (or body stream). Again, postObject:usingBlock: is just a convenience method so you don't have to remember everything.
RestKit is open source. If you are not sure how it works you are free to follow the parameters internally. If you app and web service is well designed, you should be able to use the convenience methods. Sometimes you can not, and then you can use the raw forms like you have done.
EDIT:
Q Hrm, quoting your bullet points messed up the numbers...
I solved adding a Category to RKObjectLoader, that is:
for method
-(void)getObject:(id<NSObject>)object usingBlock:(RKObjectLoaderBlock)block;
I added into the Category a modified method:
-(void)getObject:(id<NSObject>)object queryParameters:(NSDictionary*)queryParameters usingBlock:(void(^)(RKObjectLoader *))block;
Here it is the listing fpr file "RKObjectManager+QueryParameters":
//
// RKObjectManager+QueryParameters.h
// AlphaClient
//
// Created by Antonio Rossi on 14/07/12.
//
#import <RestKit/RestKit.h>
#interface RKObjectManager (QueryParameters)
- (void)getObject:(id<NSObject>)object queryParameters:(NSDictionary*)queryParameters usingBlock:(void(^)(RKObjectLoader *))block;
- (void)sendObject:(id<NSObject>)object queryParameters:(NSDictionary*)queryParameters method:(RKRequestMethod)method usingBlock:(void(^)(RKObjectLoader *))block;
#end
Here is the listing for file "RKObjectManager+QueryParameters.m":
//
// RKObjectManager+QueryParameters.m
// AlphaClient
//
// Created by Antonio Rossi on 14/07/12.
//
#import "RKObjectManager+QueryParameters.h"
#implementation RKObjectManager (QueryParameters)
- (void)getObject:(id<NSObject>)object queryParameters:(NSDictionary*)queryParameters usingBlock:(void(^)(RKObjectLoader *loader))block {
[self sendObject:object queryParameters:queryParameters method:RKRequestMethodGET usingBlock:block];
}
- (void)sendObject:(id<NSObject>)object queryParameters:(NSDictionary*)queryParameters method:(RKRequestMethod)method usingBlock:(void(^)(RKObjectLoader *))block {
NSString *resourcePath = [self.router resourcePathForObject:object method:method];
[self sendObject:object toResourcePath:resourcePath usingBlock:^(RKObjectLoader *loader) {
loader.method = method;
// need to transform the original URL because when method is GET the additional paramentes don't get added
RKURL *originalBaseURL = [RKURL URLWithBaseURL:[loader.URL baseURL]];
NSString *resourcePath = [self.router resourcePathForObject:object method:RKRequestMethodGET];
RKURL *authTokenURL = [originalBaseURL URLByAppendingResourcePath:resourcePath queryParameters:queryParameters];
[loader setURL:authTokenURL];
block(loader);
}];
}
#end
One more step is to add #import "RKObjectManager+QueryParameters.h" in your implementation file.
In this new method it is assumed that the router property of RKObjectManager has been defined before making a call to it.
I am trying to create a http body that I am going to pass in using NSURLRequest post.
I have my connection class all set up.
The thing is i have several methods that return NSStrings and UInt32's and one construction method that I want to use to put all of these methods into one http body which will be of data type format.
However I'm not sure how to call these methods that return the correct data from my construction method to gather the data into one data object.
here is some code that I have (shortened so its a little clearer)
these methods are used to return the data needed
- (UInt32) addDataVer
{
UInt32 dataVer = 0;
return dataVer;
}
- (NSString *) addReg
{
NSString *reg = [[NSString alloc] initWithString:#"abcd1"];
return reg;
}
- (NSString *) addActiv
{
NSString *activ = [[NSString alloc] initWithString:#"abcd2"];
return activ;
}
from here I'm not sure what to do, or how to get the data. I have created a construction method, that I want to use to grab the data and then I want to use that data to build a NSData object where I put the returning data into it in order.
this is my construction class
- (void) constructRequest
{
//what the heck do I call in here? lol
}
the last thing I will have to do is figure out how to put the nsdata representation of each return value into the data object... if that makes sense...
any help would be appreciated.
UPDATE::
So I figured out how to get the return value into my construction method, by following the force!
- (void) constructRequest
{
NSString *mystring = [[NSString alloc] initWithString:[self addReg]];
NSLog(#"mystring %#", mystring);
}
however I am not sure how to do this with a returning UInt32, or how to convert this in to a NSData structure
From Apple Docs on String formatting https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Strings/Articles/formatSpecifiers.html#//apple_ref/doc/uid/TP40004265-SW1
[NSString stringWithFormat:"my unsigned 32-bit int: %d", [self addDataVer]];
from Apple Docs about NSString class
To convert your entire string to data:
[myNSString dataUsingEncoding:NSUTF8StringEncoding];
I am writing a small iOS app that queries a XML REST webservice. The networking framework in use is AFNetworking.
Situation
To query the webservice I subclassed AFHTTPClient:
#interface MyApiClient : AFHTTPClient
and in the implementation I make that available as a singleton:
+ (MyApiClient *)sharedClient {
static MySharedClient *_sharedClient = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedClient = [[self alloc] initWithBaseUrl:[NSUrl URLWithString:#"http://url.to.the.webservice"]];
});
return self;
}
and in initWithBaseURL I tell AFNetworking to expect XML content:
[self registerHTTPOperationClass:[AFXMLRequestOperation class]];
Now I can call getPatch on the singleton from my ViewController and in the success block start parsing my returned XML. In NSXMLParserDelegate methods in the ViewController I can then pick the parts of the XML I am interested in and do stuff with it.
Problem
I want to have methods in my HTTPClient singleton that handle everything related to the webservice and return data models or list of models instead of XML.
For example I want to do something like this:
ServerModel *status = [[MyApiClient sharedClient] getServerStatus];
The ApiClient would then internally call the webservice, parse the XML and return the model.
How can I do that? Normally I would use a delegate that gets called once the XML is parsed, but due to the singleton nature of the ApiClient there could be multiple delegates?
Hope someone can shed light on this, thanks!
Use blocks instead of delegates.
From my ApiClient class:
- (void)getPath:(NSString *)path
parameters:(NSDictionary *)parameters
success:(void (^)(id response))success
failure:(void (^)(NSError *error))failure
{
NSURLRequest *request = [self requestWithMethod:#"GET" path:path parameters:parameters];
[self enqueueHTTPOperationWithRequest:request success:success failure:failure];
}
-(void)fetchAllUsersSuccess:(void (^)(id))success
failure:(void (^)(NSError *))failure
{
[self getPath:#"/api/mobile/user/"
parameters:nil
success:^(id response) {
if([response isKindOfClass:[NSXMLParser class]]){
//parse here to new dict
success(newDict);
} else
success(response);
} failure:^(NSError *error) {
failure(error);
}];
}
Now I can use it like:
ServiceApiClient *apiClient = [ServiceApiClient sharedClient];
[apiClient fetchAllUsersSuccess:^(id dict) {
for (NSDictionary *object in [dict objectForKey:#"objects"]) {
[ServiceUser addUserFromDictionary:object
inContext:self.managedObjectContext];
}
NSError *error= nil;
[self.managedObjectContext save:&error];
if (error) {
NSLog(#"%#", error);
}
} failure:^(NSError * error) {
NSLog(#"%#", error);
}];
(Apologies in advance for this "sort-of" answer, but we're working towards a better solution...)
You need to take a step back and think about your design carefully.
You're having problems because you've got an idea that something in your design needs to be a singleton, but either:
1) that's not actually necessary,
2) something might already exist that does that job for you (e.g. the HTTP lib you're using),
or
3) You're making the wrong thing a singleton, or you haven't portioned out your design into the appropriate parts to work well with the singleton idea
So, can you tell me explicitly why you're going for a singleton approach? Is it just to ensure that only one network request can happen at once? Is there any notion of statefulness in your singleton object? Then I'll update this answer or comment, etc.
(Digression: I would also add that in some cases there might be a true need for a 'strong' singleton -- by which I mean that there really is only one possible instance, and that mechanism is baked right into your object, as you are doing - but this isn't it. The alternative is a 'weak' singleton, by which I mean your core object that actually does the work has a plain init method as usual, but shared access to a common object goes via another object, which is a kind of simple 'factory' that instantiates/holds the shared instance. The advantage of this weak singleton idea is that your code is more re-usable in different contexts - e.g. you could decide to do multiple HTTP requests/sessions concurrently at a later time - and it sometimes makes writing tests less problematic).
I'm using TouchJSON to parse the output of a JSON Rails API, but am having difficulties. The overall goal is to loop through the response, parse the JSON, create a Round instance for each JSON object, and stick those Round objects into an NSArray so I can load this into a UITableView. So if there's a more straight-forward way to do that than what I'm about to show (which currently is NOT working, btw) please let me know.
The Rails API is returning a collection that looks something like this:
[
{
"round": { "course_title": "Title A", "result": "+8" }
},
{
"round": { "course_title": "Title B", "result": "+4" }
},
...
]
I'm also using ASIHTTPRequest and I can successfully get the response using:
NSString *responseString = [request responseString];
But from there, I cannot seem to get anywhere. Here's more-or-less what TouchJSON suggests:
NSString *jsonString = [request responseString]; // [{"round":{...}}, ..., {"round:{...}}]
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF32BigEndianStringEncoding];
NSDictionary *dictionary = [[CJSONDeserializer deserializer] deserializeAsDictionary:jsonData error:nil];
// then I do this...
NSLog(#"JSON: %#", dictionary); // JSON: null
I thought from there I would be able to loop through the dictionary and create the object mappings using my Round class. But maybe that's the wrong approach altogether.
My thoughts are that the JSON being returned from Rails is an array of JSON objects, so maybe that's why the JSON parser doesn't recognize it as valid JSON? From this, I have two questions:
1) Should TouchJSON be able to accept an array of JSON objects like what my API is returning?
2) Is it possible to cast the responseString to an NSArray so I can loop through each "round" and parse the JSON that way? If I remove the first and last characters from the response string (i.e. "[" and "]") the JSON parser will only grab the first "round" in the collection.
3) Am I going about this whole process correctly?
Any tips/advice would be much appreciated.
TouchJSON presents three main ways to go from JSON to an Obj-C object. They are all present in the header for CJSONDeserializer which you're already using:
- (id)deserialize:(NSData *)inData error:(NSError **)outError;
- (id)deserializeAsDictionary:(NSData *)inData error:(NSError **)outError;
- (id)deserializeAsArray:(NSData *)inData error:(NSError **)outError;
The first one will return return whatever, either a dictionary, array, string or whatever the root type of the JSON is.
The other two expect a dictionary or an array and will complain (i.e. return nil and give you an NSError) if they don't get the right data.
The deserializeAsDictionary:error: method of CJSONDeserializer relies on the scanJSONDictionary:error: method of CJSONScanner. This method expects the "dictionary" to be an object literal. Therefore, your data must start with a {. Since your data is an array, you would want to use the deserializeAsArray:error: method of CJSONDeserializer.
Read the documentation carefully, your code is incorrect. It should look like this:
NSData *jsonData = [request responseData]
NSArray *rounds = [[CJSONDeserializer deserializer] deserialize:jsonData error:nil];
// then I do this...
NSLog(#"JSON: %#", rounds);
You could also have used:
NSArray *rounds = [[CJSONDeserializer deserializer] deserializeAsArray:jsonData error:nil];
However your absolute BIGGEST mistake was passing nil for error. You could have avoided going to stackoverflow at ALL if you had passed something in for NSError and then checked that.
With the right tools, this is WAY simpler than you're making it. I do this sort of thing all the time.
Use Stig's JSON framework, and import the NSString category that provides the JSONValue method.
Then inside your ASIHTTPRequest response handler code, go thusly:
NSMutableArray *roundlist = [NSMutableArray array];
NSArray *results = [[request responseString] JSONValue];
for (NSDictionary *item in results) {
Round *myRound = [item objectForKey:#"round"];
//don't actually do the above. Do whatever you do to instantiate a 'Round'.
[roundlist addObject:myRound];
}
[self.tableView reloadData];
EDIT: Geezo. Objection noted re valueForKey: vs objectForKey:. I updated my code sample, and I think we all learned something here.
I also didn't mean any offense with the phrase "with the right tools". OP was looking to simplify his code, and the RIGHT TOOL for that is the library with the simplest interface. I have nothing against TouchJSON per se, but JSON Framework has the simpler interface.