Restkit NSString includes metadata - objective-c

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.

Related

RestKit Post method with JSON

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.

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

Mapping a JSON response to an object using RestKit and Objective-C

I am relatively new to Objective-C and am attempting to use RestKit to receive a JSON response from a web service. I have successfully received the data back to my application, which looks like this viewing the response:
{id:"1","Translation":"Test"}
I would like to map this translation to my "Translation" object in my application, but have tried a few different ways but am not sure how to achieve this.
So my questions are:
How can I map this response to my Translation object
Am I doing this correctly, creating a method to complete this call outwit my view controller?
My Translation Object
#implementation Translation
#synthesize identifier = _identifier;
#synthesize translation = _translation;
- (NSDictionary*)elementToPropertyMappings {
return [NSDictionary dictionaryWithKeysAndObjects:
#"id", #"identifier",
#"translation", #"translation",
nil];
}
#end
My Translate Method
- (NSString *)performTranslation:(NSString *)translation
{
NSString *data = [[NSString alloc] initWithFormat:#"{\"SourceId\": \"%#\",\"RegionTag\": \"%#\",\"InputString\": \"%#\"}", #"1", #"Glasgow", translation];
NSString *post = data;
RKRequest *MyRequest = [[RKRequest alloc] initWithURL:[[NSURL alloc] initWithString:#"http://my.url.com/Translation/Translate"]];
MyRequest.method = RKRequestMethodPOST;
MyRequest.HTTPBodyString = post;
MyRequest.additionalHTTPHeaders = [[NSDictionary alloc] initWithObjectsAndKeys:#"application/json", #"Content-Type", #"application/json", #"Accept", nil];
[MyRequest send];
RKResponse *Response = [MyRequest sendSynchronously];
return Response.bodyAsString; <--- looking to map this to translation object here
}
The snippet of your code seems a bit outdated. I strongly recommend reading the newest Object Mapping guide in order to leverage RestKit into it's fullest potential - especially the part Mapping without KVC.
Edit:
In order to post an object with RestKit and receive back an answer, we define a TranslationRequest class that will hold our request & Translation to hold our response.
Firstly, we set up our RKObjectManager and mappings (i usually do this in my AppDelegate):
RKObjectManager *manager = [RKObjectManager objectManagerWithBaseURL:kOurBaseUrl];
[manager setSerializationMIMEType:RKMIMETypeJSON];
//this is a singleton, but we keep the manager variable to avoid using [RKObjectManager sharedManager] all the time
//Here we define a mapping for the request. Note: We define it as a mapping from JSON to entity and use inverseMapping selector later.
RKObjectMapping *translationRequestMapping = [RKObjectMapping mappingForClass:[TranslationRequest class]];
[translationRequestMapping mapKeyPath:#"RegionTag" toAttribute:#"regionTag"];
...
[[manager mappingProvider] setSerializationMapping:[translationRequestMapping inverseMapping] forClass:[TranslationRequest class]];
//now we define the mapping for our response object
RKObjectMapping *translationMapping = [RKObjectMapping mappingForClass:[Translation class]];
[translationMapping mapKeyPath:#"id" toAttribute:#"identifier"];
[translationMapping mapKeyPath:#"Translation" toAttribute:#"translation"];
[[manager mappingProvider] addObjectMapping:mapping];
//finally, we route our TranslationRequest class to a given endpoint
[[manager router] routeClass:[TranslationRequest class] toResourcePath:kMyPostEndpoint];
This should be enough of the necessary setup. We can call our backend anywhere in the code (e.g. in any controller) like this:
//we create new TranslationRequest
TranslationRequest *request = [[TranslationRequest alloc] init];
[request setRegionTag:#"Hello"];
....
//then we fetch the desired mapping to map our response with
RKObjectMapping *responseMapping = [[RKObjectManager sharedManager].mappingProvider objectMappingForClass:class]
//and just call it. Be sure to let 'self' implement the required RKObjectManagerDelegate protocol
[[RKObjectManager sharedManager] postObject:request mapResponseWith:responseMapping delegate:self];]
Try this approach and let me know if you need any assistance.. I was not able to test it fully as i don't have any suitable backend that will return the responses, but judging from the RestKit log this should work.
You need to pass the returned JSON string into a JSON parser. I use SBJSON. You can then use the resulting dictionary to populate the properties of your object.
RestKit seems to have native objects that encapsulate four different JSON parsers. However, I'd advise caution because they seem to assume that the top level parsed object will always be a dictionary.
As another aside, the example in your question is not valid JSON. It should look like this:
{"id":"1","Translation":"Test"}