RestKit: mapping nsnumber - objective-c

Currently NSNumber get mapped to string, e.g.:
#interface Blah : NSObject
#property NSNumber *num;
#end
will get mapped to
{
num: "5"
}
instead of
{
num: 5
}
Does anyone know how to correctly map NSNumber to int, and NOT string?
Update:
RKObjectMapping *requestMapping = [RKObjectMapping requestMapping];
[requestMapping addAttributeMappingsFromArray:#[#"num"]];
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:requestMapping objectClass:[MyClass class] rootKeyPath:nil method:RKRequestMethodPOST];
[_manager addRequestDescriptor:requestDescriptor];

Related

Post complex json using RestKit in Objective-C

I'm having an issue posting json by using RestKit, but I can't find what I'm doing wrong.
The request json sample is:
{
"Name": "sample string 1",
"Interests": {
"Array": [
1,
2,
3
]
}
}
My code:
RKObjectMapping *requestMapping = [RKObjectMapping requestMapping];
[requestMapping addAttributeMappingsFromDictionary:#{#"Name": #"Name"}];
RKObjectMapping *interestsMapping = [RKObjectMapping mappingForClass:[NSMutableDictionary class]];
RKObjectMapping *interestIDMapping = [RKObjectMapping mappingForClass:[KLInterest class]];
[interestIDMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:nil toKeyPath:#"InterestID"]];
[interestsMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"Array" toKeyPath:#"Array" withMapping:interestIDMapping]];
[requestMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"Interests" toKeyPath:#"Interests" withMapping:interestsMapping]];
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:requestMapping objectClass:[NSMutableDictionary class] rootKeyPath:nil method:RKRequestMethodPOST];
[self.objectManager addRequestDescriptor:requestDescriptor];
NSDictionary *data = [business toDictionary];
[self sendRequest:#"api/Business/Create" Method:#"POST" Data:data withCallback:^(BOOL success, NSDictionary response, NSError error) {
callback(success, response, error);
}];
KLInterestID.h
#interface KLInterestID : NSObject
#property (nonatomic) NSNumber *InterestID;
#end
Please let me know what I'm doing wrong. I'm not familiar with the relationshipMappingFromKeyPath and attributeMappingFromKeyPath:nil functions.
I'm not sure how to send int array.
Thanks

Mapping an NSArray of NSDate with RKObjectMapping

Suppose I have this Json :
{ "arrayOfDates" : [ "7-28-2013", "7-29-2013", "7-30-2013"]}
And my object is :
#interface MyObject : NSObject
#property (nonatomic, retain) NSArray* dates;
#end
I tried to map the arrayOfDates with dates.
RKObjectMapping* mapping = [RKObjectMapping mappingForClass:[MyObject class]];
[mapping addAttributeMappingsFromDictionary:#{#"arrayOfDates" : #"dates"}];
NSDateFormatter *dateFormatter = [NSDateFormatter new];
[dateFormatter setDateFormat:#"MM-dd-yyyy"];
mapping.preferredDateFormatter = dateFormatter;
The mapping result was an array of NSString !
Is that a way to get an array of NSDate instead of NSString ?
You can't get RestKit to do it. Instead, iterate over dates in the completion block called by RestKit and use your formatter to convert the strings, then update dates.
RestKit usually translates the string to a date if the destination property is an NSDate, but in your case it's an array so RestKit doesn't know that it should be transformed.
I came across this answer trying to solve a similar problem. I found a way to get RestKit to do this using a custom RKValueTransformer.
RKObjectMapping* objectMapping = [RKObjectMapping mappingForClass:[MyObjectWithAnNSArrayProperty class]];
RKAttributeMapping* datesAttributeMapping = [RKAttributeMapping attributeMappingFromKeyPath:#"arrayOfDates" toKeyPath:NSStringFromSelector(#selector(myArrayOfDates))];
datesAttributeMapping.valueTransformer = [RKBlockValueTransformer
valueTransformerWithValidationBlock:^BOOL(__unsafe_unretained Class sourceClass, __unsafe_unretained Class destinationClass)
{
// We transform a `NSArray` into another `NSArray`
return ([sourceClass isSubclassOfClass:[NSArray class]] &&
[destinationClass isSubclassOfClass:[NSArray class]]);
}
transformationBlock:^BOOL(id inputValue, __autoreleasing id *outputValue, Class outputValueClass, NSError *__autoreleasing *error)
{
// Validate the input and output
RKValueTransformerTestInputValueIsKindOfClass(inputValue, [NSArray class], error);
RKValueTransformerTestOutputValueClassIsSubclassOfClass(outputValueClass, [NSArray class], error);
NSArray* inputValueArray = inputValue;
NSMutableArray* result = [[NSMutableArray alloc] initWithCapacity:inputValueArray.count];
// Convert strings to dates
for (NSString* inputDate in inputValueArray)
{
[result addObject:RKDateFromString(inputDate)];
}
// Get a non-mutable copy
*outputValue = [result copy];
return YES;
}];
[objectMapping addPropertyMapping:datesAttributeMapping];

Core Data & NSTableView Bindings

I am trying to bind my core data to a NSTableView. I am getting information from an API then wanting to add it to NSTableView. It looks like it is setup correctly because each time I have it call the API and get information back, a blank line is added to the NSTableView data.
Why is it adding a blank line instead of the data I have it binded too?
AppController.h
#property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;
I then am using the new Xcode where it auto synth's.
Items.h
#class TimeLog;
#interface Items : NSManagedObject
#property (nonatomic, retain) NSNumber * itemId;
#property (nonatomic, retain) NSString * title;
#property (nonatomic, retain) NSString * itemType;
#property (nonatomic, retain) TimeLog *relationship;
#end
Items.m
#implementation Items
#dynamic itemId;
#dynamic title;
#dynamic itemType;
#dynamic relationship;
#end
ItemObject.h
#interface ItemObject : NSObject
#property (nonatomic, retain) NSString * itemId;
#property (nonatomic, retain) NSString * title;
#property (nonatomic, retain) NSString * itemType;
#end
ItemObject.m
#implementation ItemObject
#end
Method making API Call
This method makes the API call and adds it to a temp object. It then adds that temp object to core data.
+ (void)searchForItemByType:(NSString *)itemType andId:(NSString *)searchId
{
NSLog(#"Search Feature By ID: %#", searchId);
RKObjectMapping *itemMapping = [RKObjectMapping mappingForClass:[ItemObject class]];
[itemMapping addAttributeMappingsFromDictionary:#{
#"id": #"itemId",
#"name": #"title",
#"item_type": #"itemType"
}];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:itemMapping pathPattern:nil keyPath:#"data" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
RKObjectMapping *errorMapping = [RKObjectMapping mappingForClass:[RKErrorMessage class]];
// The entire value at the source key path containing the errors maps to the message
[errorMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:nil toKeyPath:#"errorMessage"]];
NSIndexSet *statusCodes = RKStatusCodeIndexSetForClass(RKStatusCodeClassClientError);
// Any response in the 4xx status code range with an "errors" key path uses this mapping
RKResponseDescriptor *errorDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:errorMapping pathPattern:nil keyPath:#"error_description" statusCodes:statusCodes];
RKObjectManager *manager = [RKObjectManager sharedManager];
NSLog(#"HTTP Client: %#", manager.HTTPClient);
[manager addResponseDescriptorsFromArray:#[ responseDescriptor, errorDescriptor ]];
// NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:
#"false", #"with_lock"
, nil];
NSString *path = [NSString stringWithFormat:#"/api/v1/%#/%#", [itemType lowercaseString], searchId];
NSLog(#"Manager: %#", manager);
[manager getObjectsAtPath:path parameters:params success:^(RKObjectRequestOperation *operation, RKMappingResult *result) {
NSLog(#"Results: %#", [result firstObject]);
Items *insertItem = [NSEntityDescription insertNewObjectForEntityForName:#"Items" inManagedObjectContext:[[CoreDataHelper sharedInstance] managedObjectContext]];
insertItem = [result firstObject];
NSLog(#"Name: %#", [insertItem title]);
// Handled with articleDescriptor
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
// Transport error or server error handled by errorDescriptor
NSLog(#"Error: %#", [error localizedDescription]);
NSAlert *alert = [NSAlert alertWithMessageText:#"Error" defaultButton:#"OK" alternateButton:nil otherButton:nil informativeTextWithFormat:#"%#", [error localizedDescription]];
[alert runModal];
}];
}
Log from above code
2013-03-15 10:15:21.817 Project[59074:403] Results: <ItemObject: 0x1034ab360>
2013-03-15 10:15:21.818 Project[59074:403] ManagedObjectContext
2013-03-15 10:15:21.818 Project[59074:403] Name: Custom Mod is missing from Face Lift
IB
I think the problem may be in this code:
NSLog(#"Results: %#", [result firstObject]);
Items *insertItem = [NSEntityDescription insertNewObjectForEntityForName:#"Items" inManagedObjectContext:[[CoreDataHelper sharedInstance] managedObjectContext]];
insertItem = [result firstObject];
NSLog(#"Name: %#", [insertItem title]);
In your log it looks like [result firstObject] is part of the 'ItemObject' class not the 'Items' class. Even though they share the same structure, 'ItemObject' does not inherit from NSManagedObject, but is being assigned to one. The system doesn't know how to translate an 'ItemObject' object into an 'Items' object so it simply keeps all the values in insertItem blank, which translates into a blank line showing up in your table. Try this instead:
Items *insertItem = [NSEntityDescription insertNewObjectForEntityForName:#"Items" inManagedObjectContext:[[CoreDataHelper sharedInstance] managedObjectContext]];
ItemObject *tempObject = [result firstObject];
insertItem.itemID = tempObject.itemID;
insertItem.title = tempObject.title;
insertItem.itemType = tempObject.itemType;

Restkit: Post Json Array and Map response to Managed Objects

Here is my setup, Post a list of objects in Json to server and got back with updated results that will be mapped to core data and updated.
-- Post Boday --
{
"memberId": "1000000",
"contacts": [
{
"phoneNumber": "+12233333333",
"firstName": "john",
"lastName": "H"
},
{
"phoneNumber": "+12244444444",
"firstName": "mary",
"lastName": "K"
}
]
}
-- Post Response --
{
"contacts": [
{
"phoneNumber": "+12233333333",
"firstName": "john",
"lastName": "k",
"isMember": "yes"
},
{
"phoneNumber": "+12244444444",
"firstName": "mary",
"lastName": "k",
"isMember": "no"
}
]
}
I found another thread which discusses very similar to my case. RestKit: How does one post an array of objects?
This is my setup.
-- SHContact.h --
#interface SHContact : NSManagedObject {}
#property (nonatomic, strong) NSString* phoneNumber;
#property (nonatomic, strong) NSString* firstName;
#property (nonatomic, strong) NSString* lastName;
#property (nonatomic, strong) NSNumber* isMember;
#end
-- SHContactPost.m --
#import "SHContactPost.h"
#implementation SHContactPost
#synthesize contacts;
#synthesize memberId;
- (NSArray*)contacts {
return <list-of-SHContact>;
}
- (NSString *)memberId {
return #"my-member-id";
}
#end
-- RK Mapping --
// Setup our object mappings for SHContact
RKManagedObjectMapping* contactMapping =
[RKManagedObjectMapping mappingForClass:[SHContact class]
inManagedObjectStore:objectManager.objectStore];
contactMapping.primaryKeyAttribute = #"phoneNumber";
[contactMapping mapAttributes:#"phoneNumber",
#"firstName", #"lastName", #"isMember", nil];
[objectManager.mappingProvider setObjectMapping:contactMapping
forKeyPath:#"contacts"];
[[objectManager mappingProvider]
setSerializationMapping:[contactMapping inverseMapping]
forClass:[SHContact class]];
RKObjectMapping *cPostMapping = [RKObjectMapping
mappingForClass:[SHContactPost class]];
[cPostMapping mapKeyPath:#"contacts"
toRelationship:#"contacts"
withMapping:contactMapping];
[cPostMapping mapAttributes:#"memberId", nil];
[objectManager.mappingProvider
setSerializationMapping:[cPostMapping inverseMapping]
forClass:[SHContactPost class]];
[objectManager.router routeClass:[SHContactPost class]
toResourcePath:#"/contacts"
forMethod:RKRequestMethodPOST];
objectManager.serializationMIMEType = RKMIMETypeJSON;
-- Post to server --
SHContactPost *post = [[SHContactPost alloc] init];
RKObjectManager* manager = [RKObjectManager sharedManager];
[manager postObject:post delegate:self];
Server post and response is SUCCESSFUL. However, Reskit was not able to map the result back to the list of SHContact objects. This is the exception. Am I missing something?
restkit.network:RKObjectLoader.m:222 Encountered errors during mapping:
Cannot map a collection of objects onto a non-mutable collection.
Unexpected destination object type 'SHContactPost'
UPDATE: This is what I changed to make it work for me.
-- RK Mapping --
// Setup our object mappings for SHContact
RKManagedObjectMapping* contactMapping =
[RKManagedObjectMapping mappingForClass:[SHContact class]
inManagedObjectStore:objectManager.objectStore];
contactMapping.primaryKeyAttribute = #"phoneNumber";
[contactMapping mapAttributes:#"phoneNumber",
#"firstName", #"lastName", #"isMember", nil];
[objectManager.mappingProvider addObjectMapping:contactMapping];
-- Post to server --
SHContactPost *post = [[SHContactPost alloc] init];
RKObjectManager* manager = [RKObjectManager sharedManager];
RKObjectMapping* responseMapping = [manager.mappingProvider
objectMappingForClass:[SHContact class]];
[manager postObject:post usingBlock:^(RKObjectLoader *loader) {
loader.delegate = self;
loader.targetObject = nil;
responseMapping.rootKeyPath = #"contacts";
loader.objectMapping = responseMapping;
}];
loader.targetObject needs to set it to nil, otherwise, it won't work.

Rails RestKit POST request json's root class missing

I had this previous problem which I fixed, but I know the fix is def not done the right way. Can some one point me in the right direction for the right fix?
Basically I create a object with iOS sim via RESTkit postObject, and I got this message:
Processing PeopleController#create (for 127.0.0.1 at 2012-01-13 03:55:46) [POST]
Parameters: {"name"=>"data"}
Person Create (0.4ms) INSERT INTO "people" ("created_at", "updated_at", "name")
VALUES('2012-01-13 11:55:46', '2012-01-13 11:55:46', NULL)
Completed in 27ms (View: 1, DB: 0) | 200 OK
A nice gentlemen pointed out that my class inside my create function is only accepting a :person class which I overlooked.
def create
#person = Person.new(params[:person]) , thus looking for {"person" => {"name"=>"data"}}
I fixed this by doing
#person =Person.new(name:=>params[:name]) since i am only sending {"name"=>"data"}
now it creates ok, and I can see the entry on my ios sim. But I know this is not the right way. The right way should be {"person" => {"name"=>"data"}} sent in to the original function. Plus I get an error could not find an object mapping for keyPath:". Any thoughts?
Here is my xcode:
#interface Data: NSObject{// imaginary over arching class
Person * person;
NSArray *dog;
#property (nonatomic, retain) Person * person;
#property (nonatomic, retain) NSArray * dog;
#interface Data : NSObject {
Person *person;
NSArray *dogs;
}
#property (nonatomic ,retain) Person *person;
#property (nonatomic ,retain) NSArray *dogs;
#end
#interface Person : NSObject {
NSString *name;
NSNumber *personId;
NSDate *updatedAt;
NSDate *createdAt;
}
#property (nonatomic , retain) NSDate * createdAt;
#property (nonatomic , retain) NSDate * updatedAt;
#property (nonatomic , retain) NSNumber *personId;
#property (nonatomic , retain) NSString *name;
#end
RKObjectMapping* userMapping = [RKObjectMapping mappingForClass:[Person class]];
[userMapping mapKeyPath:#"created_at" toAttribute:#"createdAt"];
[userMapping mapKeyPath:#"updated_at" toAttribute:#"updatedAt"];
[userMapping mapKeyPath:#"name" toAttribute:#"name"];
[userMapping mapKeyPath:#"id" toAttribute:#"personId"];
RKObjectMapping* dogMapping = [RKObjectMapping mappingForClass:[Dog class]];
[dogMapping mapKeyPath:#"created_at" toAttribute:#"createdAt"];
[dogMapping mapKeyPath:#"person_id" toAttribute:#"spersonId"];
[dogMapping mapKeyPath:#"name" toAttribute:#"name"];
[dogMapping mapKeyPath:#"updated_at" toAttribute:#"updatedAt"];
[dogMapping mapKeyPath:#"id" toAttribute:#"dogId"];
RKObjectMapping *dataMapping = [RKObjectMapping mappingForClass:[Data class]];
[dataMapping mapKeyPath:#"dog" toAttribute:#"dogs"];
[dataMapping mapKeyPath:#"person" toRelationship:#"person" withMapping:userMapping];
[[RKObjectManager sharedManager].mappingProvider addObjectMapping:dataMapping];
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:#"/people"
objectMapping:dataMapping delegate:self];
RKObjectRouter * router = [RKObjectManager sharedManager].router;
[router routeClass: [Person class] toResourcePath:#"/people/:personId"];
[router routeClass: [Person class] toResourcePath:#"/people"
forMethod:RKRequestMethodPOST];
RKObjectMapping *personSerializationMapping = [RKObjectMapping mappingForClass:
[NSMutableDictionary class]];
[personSerializationMapping attribute:#"name", nil];
[RKObjectManager sharedManager].mappingProvider
setSerializationMapping:personalSerializationMapping forClass: [Person class]];
Person *dave = [[Person alloc]init];
dave.name = #"Dave";
[[RKObjectManager sharedManager] postObject:dave delegate:self];
}
Take Person as an example:
RKObjectMapping* userMapping = [RKObjectMapping mappingForClass:[Person class]];
[userMapping mapKeyPath:#"created_at" toAttribute:#"createdAt"];
[userMapping mapKeyPath:#"updated_at" toAttribute:#"updatedAt"];
[userMapping mapKeyPath:#"name" toAttribute:#"name"];
[userMapping mapKeyPath:#"id" toAttribute:#"personId"];
To config RestKit to POST with a root path, replace:
[[[RKObjectManager sharedManager] mappingProvider]
setSerializationMapping:personalSerializationMapping
forClass:[Person class]];
with:
[[[RKObjectManager sharedManager] mappingProvider]
registerMapping:objectMapping
withRootKeyPath:#"person"];
Now Rails will receive {"person" => {"name"=>"data"}} instead of {"name"=>"data"}.
For reference, here is the magic of registerMapping:
- (void)registerObjectMapping:(RKObjectMapping *)objectMapping withRootKeyPath:(NSString *)keyPath {
// TODO: Should generate logs
objectMapping.rootKeyPath = keyPath;
[self setMapping:objectMapping forKeyPath:keyPath];
RKObjectMapping* inverseMapping = [objectMapping inverseMapping];
inverseMapping.rootKeyPath = keyPath;
[self setSerializationMapping:inverseMapping forClass:objectMapping.objectClass];
}
it does both setMapping and setSerializationMapping with the clever [objectMapping inverseMapping] trick.
Yea finally figured it out. The mapping was wrong. Instead of addObjectMapping: dataMapping, it should have been userMapping with keyPath person which is userMapping. btw thnx #favo for editing my chicken scratch =] now it can GET and POST fine!
For completionist sake, the error about "could not find mapping for key path ''" seems to be a thing with Rails create action.
There's a good answer for this problem in this SO question: how to post an object to Rails using RESTKit