RestKit Post method with JSON - objective-c

I'm developing an iOS App for school.
I'm using a database so i can run some statistics later.
I created a Restful Web Service to handle all functions I need and using RestKit to access the Web Service.
When I need to retrieve data from WS i dont have any problem to do it, but when i need to post info I am getting some errors that I would like some help if you can.
The POST method I created in WS is for adding a new Collection to DB, it has no return, just add it. I'm using the GSON library to convert from JSON. I tested with the "tester" from netbeans and worked well so I guess the problem is not on the Web Service. I'll put the code for the classes involved and the method that is trying to POST de object.
I have this class: Collection
#import <Foundation/Foundation.h>
#import "User.h"
#interface Collection : NSObject
#property NSNumber *idCollection;
#property NSString *name;
#property User *user;
#property NSArray *collectionItens;
#end
And this class: User
#import <Foundation/Foundation.h>
#interface User : NSObject
#property NSNumber *idUser;
#property NSString *login;
#property NSString *password;
#end
Both class only have the #syntezise
Here is the method im trying to post the object:
- (IBAction)createNewCollection:(id)sender
{
NSLog(#"..");
Collection *collection = [[Collection alloc] init];
collection.name = collectionNameTextField.text;
collection.user = [AppDefauts defaultUser];
RKObjectMapping *userMapping = [RKObjectMapping requestMapping];
[userMapping addAttributeMappingsFromArray:#[ #"idUser", #"login", #"password"]];
RKObjectMapping *collectionMapping = [RKObjectMapping requestMapping];
[collectionMapping addAttributeMappingsFromArray:#[ #"idCollection", #"name"]];
[collectionMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"user" toKeyPath:#"user" withMapping:userMapping]];
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:collectionMapping objectClass:[Collection class] rootKeyPath:#"collection" method:RKRequestMethodAny];
RKObjectManager *objectManager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:#"http://localhost:8080/MTGAppWS/webresources"]];
[objectManager addRequestDescriptor:requestDescriptor];
[objectManager postObject:collection path:#"/Collection" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"Success");
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Error");
}];
}
Log display on Xcode with error:
http://pastebin.com/JXCYzF8B
Web Service code for include a new Collection
#Path("Collection")
public class CollectionService
{
}
#Context
private UriInfo context;
/**
* Creates a new instance of CollectionService
*/
public CollectionService()
{
}
#PUT
#Consumes("application/json")
public void createNewCollection(String content)
{
CollectionController c= new CollectionController(new SQLController());
Gson gson = new Gson();
JsonParser parser = new JsonParser();
parser.parse(content);
Collection collection = gson.fromJson(content, Collection.class);
c.criarNovaCollection(collection);
}
}
EDIT
Found out that the request wasn't "going" to right address.
already fixed that but now its happening another error. (Method not allowed).
Error message: http://pastebin.com/99FiNmQZ

The server you are communicating with is giving you an HTML error page instead of the JSON you were expecting: The requested resource () is not available. The error isn't in your app (unless you are asking for the wrong URL); it's on the server.

Related

worklight 6.0 adapter native ios to hybrids application

I can able to call worklight 6.0 adapter invoke in IOS native code (using Objective-C) but i can not read adapter JSON response using cordova plugin from my hybrids application.
// invoke the adapter
MyConnectListener *connectListener = [[MyConnectListener alloc] initWithController:self];
[[WLClient sharedInstance] wlConnectWithDelegate:connectListener];
// calling the adapter using objective-c
WLProcedureInvocationData *myInvocationData = [[WLProcedureInvocationData alloc] initWithAdapterName:#"HTTP_WS_ADPTR" procedureName:#"getBalance"];
MyInvokeListener *invokeListener = [[MyInvokeListener alloc] initWithController: self];
[[WLClient sharedInstance] invokeProcedure:myInvocationData withDelegate:invokeListener];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:responseString];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
// hybrids application call the native code
cordova.exec(sayHelloSuccess, sayHelloFailure, "HelloWorldPlugin", "sayHello", [name]);
above the cordova.exec success and failed method is not return the value.
but i can not able to parse the value from CDVPluginResult method. Anybody please advice me. how can i read the adapter for IOS native from hybrids application.
Several things to note:
You are using Worklight 6.0.0.x. In Worklight 6.0.0.x there is no proper session sharing between web and native views. Meaning, if you will for example call the WL.Client.connect() method in the web view and then do a connect() and adapter invocation in the native view - these calls will not share the same session which can lead to race condition errors, inability to share state between the views and other unexpected events. Not recommended.
If this is the approach you're looking to implement in your Hybrid application it is therefore highly recommended that you will upgrade to either MobileFirst (previous known as "Worklight") v6.3 or v7.0 where session sharing between web and native views is now available out-of-the-box.
Although you might just want to opt to call the adapter from the JS code...
To get this to work as-is in your supplied project, you can change the implementation based on the below.
Note that the implementation below was based on MFP 7.0, as such the adapter invocation code will not work in your 6.0.0.0x codebase. You will need to alter it based on your own code in v6.0.0.x:
sayHello.h
#import <Foundation/Foundation.h>
#import <Cordova/CDV.h>
#import "WLClient.h"
#import "WLDelegate.h"
#interface SayHelloPlugin : CDVPlugin
- (void)sayHello:(CDVInvokedUrlCommand*)command;
- (void)callResult:(NSString*)response;
#end
sayHello.m
#import "SayHelloPlugin.h"
#import "MyConnectListener.h"
CDVInvokedUrlCommand *tempCommand;
#implementation SayHelloPlugin
- (void)sayHello:(CDVInvokedUrlCommand*)command {
MyConnectListener *connectListener = [[MyConnectListener alloc] init:self];
[[WLClient sharedInstance] wlConnectWithDelegate:connectListener];
tempCommand = command;
}
-(void)callResult:(NSString*)response{
CDVPluginResult *pluginResult =
[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:response];
[self.commandDelegate sendPluginResult:pluginResult callbackId:tempCommand.callbackId];
}
#end
MyConnectListener.h
#import <Foundation/Foundation.h>
#import "WLClient.h"
#import "WLDelegate.h"
#import "SayHelloPlugin.h"
#interface MyConnectListener : NSObject <WLDelegate> {
#private
SayHelloPlugin *sh;
}
- (id)init: (SayHelloPlugin *)sayHello;
#end
MyConnctListener.m
The responseText line is commented out because the data retrieved from the adapter was too large I suppose, so it's best to return only what you really need and not all of it.
#import "MyConnectListener.h"
#import "WLResourceRequest.h"
NSString *resultText;
NSString *request;
#implementation MyConnectListener
- (id)init: (SayHelloPlugin *) sayHello{
if ( self = [super init] )
{
sh = sayHello;
}
return self;
}
-(void)onSuccess:(WLResponse *)response{
NSURL* url = [NSURL URLWithString:#"/adapters/testAdapter/getStories"];
WLResourceRequest* request = [WLResourceRequest requestWithURL:url method:WLHttpMethodGet];
[request setQueryParameterValue:#"['technology']" forName:#"params"];
[request sendWithCompletionHandler:^(WLResponse *response, NSError *error) {
if(error != nil){
resultText = #"Invocation failure: ";
resultText = [resultText stringByAppendingString: error.description];
[sh callResult:resultText];
}
else{
resultText = #"Invocation success. ";
//resultText = [resultText stringByAppendingString:response.responseText];
[sh callResult:resultText];
}
}];
}
-(void)onFailure:(WLFailResponse *)response{
resultText = #"Connection failure: ";
resultText = [resultText stringByAppendingString:[response errorMsg]];
NSLog(#"***** failure response: %#", resultText);
[sh callResult:resultText];
}
#end

Restkit NSString includes metadata

I have a JSON file that i'm downloading from the web and parsing using RestKit. My problem is, I have a property with an array of strings that i'm trying to map into their own object. For some reason, when parsing that string, it ends up including the metadata object's description in the string. Please see my code snippets below for what i'm currently using, and the errors i'm experiencing. Note: I removed any core data integration and made my test as bare-bones as I could to attempt to track down the issue.
Object interfaces
#interface BLAuthor : NSObject
#property (nonatomic, retain) NSString *name;
#end
#interface BLVolume : NSObject
#property (nonatomic, retain) NSString *id;
#property (nonatomic, retain) NSSet *authors;
#end
Mapping and request operation
RKObjectMapping *volumeMapping = [RKObjectMapping mappingForClass:[BLVolume class]];
[volumeMapping addAttributeMappingsFromDictionary:#{ #"id": #"id" }];
RKObjectMapping *authorMapping = [RKObjectMapping mappingForClass:[BLAuthor class]];
[authorMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:nil
toKeyPath:#"name"]];
[volumeMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"volumeInfo.authors"
toKeyPath:#"authors"
withMapping:authorMapping]];
NSIndexSet *statusCodes = RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful);
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:volumeMapping
pathPattern:nil
keyPath:#"items"
statusCodes:statusCodes];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"https://example.com/?q=123"]];
RKObjectRequestOperation *operation = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:#[responseDescriptor]];
[operation setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *result) {
NSLog(#"Success! %#", result);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Failed with error: %#", [error localizedDescription]);
}];
NSOperationQueue *operationQueue = [NSOperationQueue new];
[operationQueue addOperation:operation];
JSON snippet
{
"items": [
{
"id": "abc",
"volumeInfo": {
"authors": [
"123"
]
}
}
Resulting data - please note, everything after <BLAuthor = 0x08466250 | name = is actually part of the NSString property on BLAuthor:
Success! <RKMappingResult: 0x8468560, results={
items = (
"<BLVolume = 0x08463F60 | id = mikPQFhIPogC | authors = {(
<BLAuthor = 0x08466250 | name = 123 ({
HTTP = {
request = {
URL = \"https://example.com/?q=123";
headers = {
};
method = GET;
};
response = {
URL = \"https://example.com/?q=123\";
headers = {
\"Cache-Control\" = \"private, max-age=0, must-revalidate, no-transform\";
\"Content-Type\" = \"application/json; charset=UTF-8\";
Date = \"Sun, 23 Jun 2013 00:41:01 GMT\";
Etag = \"\\\"I09ELXbrmOlE-RFCkDsRbIJj278/gPh8_OxpfA9YHXz_P_25F8A4orw\\\"\";
Expires = \"Sun, 23 Jun 2013 00:41:01 GMT\";
Server = GSE;
\"Transfer-Encoding\" = Identity;
\"X-Content-Type-Options\" = nosniff;
\"X-Frame-Options\" = SAMEORIGIN;
Thanks in advance to anyone who can help me resolve this! I'm at wits end - tried to remove as many variables from my testing as I can, and have searched both the web and RestKit's source looking for the cause.
The problem is with this mapping
[authorMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:nil
toKeyPath:#"name"]];
You're basically saying: "map whatever you find onto the name property".
This can only work in case you're matching the path as opposed to the key in the response descriptor, as suggested here. But in order to do so, you would need the response to return the string array standalone, which is not your case.
As per the same post, you can try with
[authorMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:#""
toKeyPath:#"name"]];
but I am skeptical it could work.
If you have control over the APIs, I'd suggest to change the format to a more RESTful one, by returning full-fledged resources instead of plain strings inside the array.
So, i've come to a sad conclusion - nothing is broken, my code works perfectly.
With relationships, apparently RestKit sets your objects to an NSProxy subclass. The subclass overrides -description and returns #"%# (%#)", originalObject, metadata. So when logging the description of the object, I get that, instead of the ACTUAL objects' value. Very irritating and difficult to track down. I'm going to be opening an issue on RestKit to remove this confusing description, as when building my app without a UI yet, logging the description of the actual object is important.

How to do in Restkit a PUT request with no body, url params, and get back an object

Lets say that I have to do this dynamic PUT request:
"http://mydomain.com/api/5?value=66"
The body is empty.
I will get in return a 201 (Created) status, and in the body I'm getting back a json object,
Let's call it MyObject that has fields NSNumber* Id, NSString* name;
Now in restkit I have these options:
- [[RKObjectManager sharedManager] putObject:nil mapResponseWith:MyMapping delegate:self];
MyMapping maps MyObject.
The problem is that if I'm sending nil, it doesn't know the mapping and throws "Unable to find a routable path for object of type '(null)' for HTTP Method 'PUT'"
- [[RKClient sharedClient] put:putUrl params:nil delegate:self];
where putUrl = "http://mydomain.com/api/5?value=66"
The problem here is that there is no mapping for the response so only didLoadResponse is called back and didLoadObjects never called
[objectManager.router routeClass:[MyObject class] toResourcePath:putUrl forMethod:RKRequestMethodPUT];
MyObject *obj = [[MyObject alloc] init];
[[RKObjectManager sharedManager] putObject:obj mapResponseWith:MyMapping delegate:self];
The problem here is that first that I fake it (send MyObject as param while it isn't) and it works only for the first time. for the second time I'm trying to use this method I'm getting this exception: "A route has already been registered for class 'MyObject' and HTTP method 'PUT'"
Any suggestion what to do?
Thanks
If anyone is intersted I found the answer after seeing what restkit is doing.
putUrl = "http://mydomain.com/api/5?value=66";
MyMapping maps the returned MyObject that has fields NSNumber* Id, NSString* name;
Here is the code to get it working:
void (^blockLoader)(RKObjectLoader *);
blockLoader = ^(RKObjectLoader *loader) {
loader.delegate = self;
loader.objectMapping = MyMapping;
};
NSString *resourcePath = putUrl;
[[RKObjectManager sharedManager] sendObject:nil toResourcePath:resourcePath usingBlock:^(RKObjectLoader *loader) {
loader.method = RKRequestMethodPUT;
blockLoader(loader);
}];

RestKit many to many relationship saves new rows in both join table and null values in main table

I use RestKit to cache data from a remote server locally. In it I have a many to many relationship between Category <<-->> News. The mapping seems to work properly, despite that it also saves null values in my Category table (it saves the correct categories too). Like the image below:
It seems to save 30 null rows, I also have 30 (not null) rows in my join table so there might be a correlation here.
The JSON that I get looks like this: "categories":[{"category_id":1},{"category_id":4}]
I have two custom model objects that inherits from NSManagedObject.
#interface News : NSManagedObject
[...]
#property (nonatomic, retain) NSSet *categories;
#end
#interface Category : NSManagedObject
[...]
#property (nonatomic, retain) NSSet *news;
#end
I use #dynamic on both.
My mappings looks like this:
RKManagedObjectMapping *categoryMapping = [RKManagedObjectMapping mappingForClass:[Category class] inManagedObjectStore:objectManager.objectStore];
categoryMapping.primaryKeyAttribute = #"categoryId";
categoryMapping.rootKeyPath = #"categories";
[categoryMapping mapKeyPath:#"id" toAttribute:#"categoryId"];
[...]
RKManagedObjectMapping* newsMapping = [RKManagedObjectMapping mappingForClass:[News class] inManagedObjectStore:objectManager.objectStore];
newsMapping.primaryKeyAttribute = #"newsId";
newsMapping.rootKeyPath = #"news";
[newsMapping mapKeyPath:#"id" toAttribute:#"newsId"];
[...]
// Categories many-to-many (not fully working yet).
[newsMapping mapKeyPath:#"categories" toRelationship:#"categories" withMapping: categoryMapping];
// Register the mappings with the provider
[objectManager.mappingProvider setObjectMapping:newsMapping forResourcePathPattern:#"[path-to-JSON]"];
[objectManager.mappingProvider setObjectMapping:categoryMapping forResourcePathPattern:#"[path-to-JSON]"];
I fetch the data like this (much like the Twitter RestKit example):
- (id)init
{
self = [super init];
if (self) {
[self loadCategories];
[self loadCategoriesFromDataStore];
[self loadNews];
[self loadNewsFromDataStore];
}
return self;
}
- (void)loadCategoriesFromDataStore
{
NSFetchRequest* request = [Category fetchRequest];
NSSortDescriptor* descriptor = [NSSortDescriptor sortDescriptorWithKey:#"categoryId" ascending:YES];
[request setSortDescriptors:[NSArray arrayWithObject:descriptor]];
_categories = [Category objectsWithFetchRequest:request];
}
- (void)loadNewsFromDataStore
{
NSFetchRequest* request = [News fetchRequest];
NSSortDescriptor* descriptor = [NSSortDescriptor sortDescriptorWithKey:#"createdAt" ascending:NO];
[request setSortDescriptors:[NSArray arrayWithObject:descriptor]];
_news = [News objectsWithFetchRequest:request];
}
- (void)loadCategories
{
// Load the object model via RestKit
RKObjectManager *objectManager = [RKObjectManager sharedManager];
[objectManager loadObjectsAtResourcePath:#"[link-to-JSON]" delegate:self];
}
- (void)loadNews
{
// Load the object model via RestKit
RKObjectManager *objectManager = [RKObjectManager sharedManager];
[objectManager loadObjectsAtResourcePath:#"[link-to-JSON]" delegate:self];
}
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects {
[[NSUserDefaults standardUserDefaults] synchronize];
[self loadCategoriesFromDataStore];
[self loadNewsFromDataStore];
}
Any ideas what I'm doing wrong?
Note:
It also seems to save 30 rows to the join table when there are 15 News, I don't know if this is the normal behavior for join tables.
Update: It also maps the the wrong category id's in the join table (ie. category 66, a null row).
update 2: JSON get request for my Category {"categories":{[...], "id":1, [...]}
This line is wrong:
[categoryMapping mapKeyPath:#"id" toAttribute:#"categoryId"];
it should be
[categoryMapping mapKeyPath:#"category_id" toAttribute:#"categoryId"];
EDIT:
The object mapping you have will map two JSON formats.
1) {"categories":[{"id":7},{"id":12}]} when you have the resource path #"[path-to-JSON]"
2) {"news":[{"id":5, "categories":[{"id":7}]}]} when you have the resource path #"[path-to-JSON]"
(I presume those actual paths are different by the way)
Anything else, such as the JSON you have posted at the top of your question, will not work. The problem you are seeing is because the primary key attribute can not be found in your JSON, so when the object is saved to core data a new entity is created with no primary key.

How to change JSON before mapping by RESTKIT

I am using RESTKIT to map the JSON returned from server.
The JSON result obtained from server is as follows
{"term":"Zh","results":{"users":[{"id":{"$oid":"4ebe59970a614e0019000055"},"term":"some text","score":1}]}
How can I convert the above JSON result to the below:
{"results":{"users":[{"uid":"4ebe59970a614e0019000055","text":"some text"}]}
Also, where can I do this so that the RESTKIT mapping will use the converted JSON instead of the initial one?
Below is the loader class that I am using to manage the JSON and mappings
-(void)getObjects
{
RKObjectManager *sharedManager = [RKObjectManager sharedManager];
[sharedManager loadObjectsAtResourcePath:self.resourcePath delegate:self];
}
- (void)request:(RKRequest*)request didLoadResponse:(RKResponse*)response {
NSLog(#"Loaded PAYLOAD successfully for %#, with response %#", self.resourcePath , [response bodyAsString] );
}
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects
{
}
+ (void)setManagerAndMappings
{
RKObjectManager* manager = [RKObjectManager objectManagerWithBaseURL:SERVER_URL];
RKObjectMappingProvider* provider = [[RKObjectMappingProvider new] autorelease];
//User Object Mapping
RKObjectMapping* userMapping = [RKObjectMapping mappingForClass:[User class]];
[userMapping mapKeyPath:#"_id" toAttribute:#"uid"];
[userMapping mapAttributes:#"avatar_url",#"created_at",#"email",#"name",#"nickname",#"follower_count",#"following_count",#"answer_count",#"new_notification_count",#"new_questions_count",#"is_following",#"facebook_configured",#"twitter_configured",#"description",#"approved",#"type", nil];
[provider setMapping:userMapping forKeyPath:#"user"];
}
#Shocks answer is appealing, unfortunately it is valid a for version before 0.20.
Anyway, a similar solution is available for 0.20 too, using an implementation of RKSerialization:
#interface ORRKJsonSerialization : NSObject <RKSerialization>
#end
and implementing
#implementation ORRKJsonSerialization
+ (id)objectFromData:(NSData *)data error:(NSError **)error
{
id result = [NSJSONSerialization JSONObjectWithData:data options:0 error:error];
// change your data before mapping
return result;
}
+ (NSData *)dataFromObject:(id)object error:(NSError **)error
{
return [NSJSONSerialization dataWithJSONObject:object options:0 error:error];
}
#end
then during the setup:
[RKMIMETypeSerialization registerClass:[ORRKJsonSerialization class] forMIMEType:#"application/json"];
HTH
There is a willMapData: selector in the RKObkectLoaderDelegate that is invoked just after parsing has completed. The mappableData argumet is mutable, so i guess you can change the data just before the object mapping will take place.
use RKObjectRequestOperation.setWillMapDeserializedResponseBlock:.
In swift:
let request = RKObjectManager.sharedManager().requestWith...
let operation = RKObjectManager.sharedManager().managedObjectRequestOperationWithRequest(request, managedObjectContext: context, success: { operation, result in
// success
}, failure: { operation, error in
// failure
})
operation.setWillMapDeserializedResponseBlock { deserializedResponse -> AnyObject! in
// Here to transform response
return transformResponse(deserializedResponse)
}
RKObjectManager.sharedManager().enqueueObjectRequestOperation(operation)
For me the only solution is to modify the returned object at the server level.
If you can't,just map that returns the server.
You can register your own parser. I had to do this to modify and clean-up some JSON from a legacy system.
[[RKParserRegistry sharedRegistry] setParserClass:[MyJSONParser class]
forMIMEType:#"application/json"];
And then create your own:
#import <RestKit/RestKit.h>
#import <RestKit/RKJSONParserJSONKit.h>
#import <RestKit/JSONKit.h>
#import <RestKit/RKLog.h>
#interface EFJSONParser : RKJSONParserJSONKit
- (NSDictionary *)objectFromString:(NSString *)string error:(NSError **)error;
#end
It's very difficult to editing the response of JSOn. the better way is only to do changes in server side as said by #beber