Post multiple Objects with RestKit (as JSON-Array) - ios

I'm trying to send multiple objects as an array to a Server with RestKit. Unfortunately I'm not able to do so.
Following my pretty simple objects as well as the mapping for RestKit:
Example Objects
#interface MyExampleObject : NSObject
#property NSString *key;
#property NSString *value;
#end
Array-Object holding multiple of MyExampleObject
#interface MyArray : NSObject
#property NSArray *array;
#end
Mapping
RKObjectMapping *mappingObject = [RKObjectMapping mappingForClass:[MyExampleObject class]];
[mappingObject addAttributeMappingsFromDictionary:#{
#"key" : #"key",
#"value" : #"value"
}];
RKObjectMapping *mappingArray = [RKObjectMapping mappingForClass:[MyArray class]];
[mappingArray addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"dummy" toKeyPath:#"array" mappingObject]];
If I do it this way I’ll get the following result:
{
"dummy" : [
{
"key" : "MyKey1",
"value" : "MyValue1"
},
{
"key" : "MyKey2",
"value" : "MyValue2"
}
]
}
But I want only the array without a „key“. Like this:
[
{
"key" : "MyKey1",
"value" : "MyValue1"
},
{
"key" : "MyKey2",
"value" : "MyValue2"
}
]
It seemed obvious for me to change the relationshipMappingFromKeyPath to nil. But this didn't worked (got a setObjectForKey: key cannot be nil error).
What do I have to do to send multiple MyExampleObjects to my Server as a JSON-Array?
Solution:
As Wain suggested I've removed my "Top-Mapping". Following the final mapping:
RKObjectMapping *mappingObject = [RKObjectMapping mappingForClass:[MyExampleObject class]];
[mappingObject addAttributeMappingsFromDictionary:#{
#"key" : #"key",
#"value" : #"value"
}];
And when I post the stuff to my Server I just do something like this:
NSArray *array = [NSArray arrayWithObjects:myExampleObject1, myExampleObject2, nil];
[[RKObjectManager sharedManager] postObject:array path:#"/myPath/" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
...
}

Just get rid of the MyArray object and the associated mapping and directly pass the NSArray of MyExampleObjects to the post method. RestKit will understand that it's an array of objects to map and do the right thing.

Related

RestKit additional data in response

I am using RestKit for mapping data from my api to CoreData entities and i wonder how can i get additional data from response. For example my api return structure like:
{
"items": [
{
"id": 1,
"title": "Title"
},
{
"id": 2,
"title": "Title 2"
}
],
"someParameter": "someValue"
}
i already have right mappings for shared object manager so i just send request:
[[RKObjectManager sharedManager] getObjectsAtPath:#"_api/items"
parameters:parameters
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
//handle success
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
//handle error
}];
How can i get someParameter value in success block? Is this possible?
You will need to tweak your mapping slightly. If you changed it as follows you should be able to get RESTkit to parse the 'someParameter' attribute for you. You need to have two classes (Parent and Child).
The Parent class has 2 attributes (someParameter and an array of Child objects). The addRelationshipMappingWithSourceKeyPath is what ties the Parent and Child object mappings together.
Code:
RKObjectMapping *parentMapping = [RKObjectMapping mappingForClass:[Parent class]];
[beaconActionMapping addAttributeMappingsFromDictionary:#{
#"someParameter" : #"someParameter"
}];
RKObjectMapping *childMapping = [RKObjectMapping mappingForClass:[Child class]];
[beaconMapping addAttributeMappingsFromDictionary:#{
#"id" : #"childId",
#"title" : #"title"
}];
[parentMapping addRelationshipMappingWithSourceKeyPath:#"items" mapping:childMapping];
Class hierarchy:
#interface Parent : NSObject
#property(nonatomic,strong) NSString *someParameter;
#property(nonatomic,strong) NSArray *items; // Array of Child objects
#end
#interface Child : NSObject
#property(nonatomic,strong) NSString *childId;
#property(nonatomic,strong) NSString *title
#end
You can add an additional response descriptor with a key path of someParameter. You can use that with a nil key path mapping to extract the string value into an object of your choice (usually a custom class).

RestKit RKObjectMapping of a list

I am new to RestKit and am trying to map a json object that contains an array of objects my model. I have debugged and found that the response hits my server --> json is return --> RestKit says the mapping was successful and that I have 1 object mapped... However, the errorCode field and the array of businesses (bList) are both null when I do BusinessObjectModel *response = result.array.firstObject;
in the OnSuccessBlock.
Json:
{
"bList":
[
{
"id": 1,
"name": "aName",
"owner": 1,
"category": 1,
}
{
"id": 2,
"name": "aName2",
"owner": 1,
"category": 1,
}
],
"errorCode": 0
}
Want to map this Json to this objective c object:
BussinessObjectModel Object Mapping:
RKObjectMapping *responseMapping = [RKObjectMapping mappingForClass:[BusinessObjectModel class]];
[responseMapping addAttributeMappingsFromDictionary:#{
#"id": #"business_id",
#"name": #"business_name",
}];
return responseMapping;
BModel:
#interface bModel : NSObject
#property (nonatomic, copy) NSNumber *errorCode;
#property (nonatomic, copy) NSMutableArray *bList;
+(RKObjectMapping *) getMapping;
#end
BModel Object Mapping:
RKObjectMapping *bMapping = [BusinessObjectModel getMapping];
RKObjectMapping *buslstMapping = [RKObjectMapping mappingForClass:[BModel class]];
[buslstMapping addAttributeMappingsFromDictionary:#{#"errorCode": #"errorCode"}];
// Define the relationship mapping
[buslstMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:nil
toKeyPath:nil
withMapping:bussinessMapping]];
return buslstMapping;
Descriptor looks as follows:
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:ResponseMapping method:RKRequestMethodAny pathPattern:nil keyPath:#"bList" statusCodes:nil];
EDIT
I want to map the above Json to :
#interface bModel : NSObject
#property (nonatomic, copy) NSNumber *errorCode;
#property (nonatomic, copy) NSMutableArray *bList;
#end
Where bList is an array of the following object:
#interface Business : NSObject
#property (nonatomic, copy) NSNumber *id;
#property (nonatomic, copy) NSString *name;
#property (nonatomic, copy) NSNumber *id;
#property (nonatomic, copy) NSNumber *id;
#end
I guess the question is how do I do nested relationships (what would the Response Descriptor have to be for the above relationship)?
Great to see that your using RestKit. Your first helper will be the logging that RestKit provides. Add this line of code do your AppDelegate and watch the console for errors.
// The * will send everything RestKit does to the console
// Replace the * with the module you want to check (Network/CoreData/...)
RKLogConfigureByName("RestKit/*", RKLogLevelTrace);
When looking at your JSON you want to get the objects from the keyPath "bList" on the one hand and on the other hand the error code or message when something goes wrong. RestKit provides a build in error handling to get that information out of your JSON.
Init the RKObjectMapping for the RKErrorMessage class and add a RKResponseDescriptor to your requests with the right keyPaths including the range of status codes (here using client errors). RestKit will automatically detect the error code (when sent within the header) and apply the mapping to get the content of the error message.
// Init error mapping
RKObjectMapping *errorMapping = [RKObjectMapping mappingForClass:[RKErrorMessage class]];
[errorMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:nil toKeyPath:#"errorMessage"]];
// Add mapping as response descriptor
RKResponseDescriptor *errorDescriptor =
[RKResponseDescriptor responseDescriptorWithMapping:errorMapping
method:RKRequestMethodGET
pathPattern:nil
keyPath:#"message" // Edit the keyPath to the value of your JSON (e.g. errorCode)
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassClientError)];
[RKObjectManager.sharedManager addResponseDescriptor:errorDescriptor];
To get the error message when a failure occurs, you simply get the message object. When using a block to get objects, you can use the userInfo dictionary from the given NSError.
RKErrorMessage *errorMessage = [[error.userInfo objectForKey:RKObjectMapperErrorObjectsKey] firstObject];
NSLog(#"Error: %#", errorMessage);
Now you can simplify your object model a bit and concentrate on mapping the BusinessObjectModel. When mapping the object using the dictionary, you need to check of your local attributes matches the value in your JSON.
RKObjectMapping *responseMapping = [RKObjectMapping mappingForClass:[BusinessObjectModel class]];
[responseMapping addAttributeMappingsFromDictionary:#
{
#"value_from_remote_json": #"value_in_local_object", // e.g. #"id" : #"business_id"
...
}];
return responseMapping;
You don't need to use a RKRelationshipMapping any more. Reconfigure your objects/mappings and try again. The logging will show you the provided mapping and if the mapping operations are working. Last bit not least make sure that the mapping is in memory when using it by throwing an NSAssert error.
RKObjectMapping *bMapping = [BusinessObjectModel getMapping];
NSAssert(bMapping, #"bMapping mapping must not be nil");
Edit
To map values without a keyPath (like the "errorCode" field) additionally to the mapping of your objects, you'll need to provide an object with an according mapping. Taking the example from the documentation you'll end with something like:
// Init object
#interface RKErrorCode : NSObject
#property (nonatomic) NSNumber *errorCode;
#end
// Init mapping
RKObjectMapping *codeMapping = [RKObjectMapping mappingForClass:[RKErrorCode class]];
[codeMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:nil toKeyPath:#"errorCode"]];
// Add response descriptor for request
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:codeMapping method:RKRequestMethodAny pathPattern:nil keyPath:#"errorCode" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[[RKObjectManager sharedManager] addResponseDescriptor:responseDescriptor];
Your response descriptor shouldn't have keyPath:#"bList", it should be set to nil as you don't want to drill in.
It also shouldn't use ResponseMapping based on the structure of your mappings, it should use the other mapping.
Your mapping is also wrong here:
#{
#"business_id": #"business_id",
#"business_name": #"business_name",
}];
It should be:
#{
#"id": #"business_id",
#"name": #"business_name",
}];
Because this specifies the JSON names and the core data names.
To fix your new issues, put this back:
[buslstMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"bList" toKeyPath:#"bList"
And this:
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:buslstMapping method:RKRequestMethodAny pathPattern:nil keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
Found the solution and it looks something like this:
RKObjectMapping *bModelMapping = [RKObjectMapping mappingForClass:[business class]];
[bModelMapping addAttributeMappingsFromDictionary:#{
#"id": #"business_id",
#"name": #"business_name",
}];
RKObjectMapping *buslstMapping = [RKObjectMapping mappingForClass:[bModel class]];
[buslstMapping addAttributeMappingsFromDictionary:#{#"errorCode": #"errorCode"}];
// Define the relationship mapping
[buslstMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"bList"
toKeyPath:#"bList" bModelMapping]];
return buslstMapping;
Also Change the following:
#property (nonatomic, copy) NSMutableArray *bList;
to
#property (nonatomic, retain) NSArray *bList;
This gives me a bModel object with an error code value and a bList which is an array of business which can then be cast to a business by doing the following Business *business = [bModel.blist firstObject];

RestKit : how to map an array with correct type?

Got:
#property(nonatomic, retain) NSString *user;
#property(nonatomic, retain) NSString *token;
#property(nonatomic, retain) NSArray *list;// NSNumber only please!
And:
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[MyResponse class]];
[mapping addAttributeMappingsFromDictionary:#{
#"user" : #"user",
#"token" : #"token",
#"list" : #"list",
}];
And I receive Json:
{"user":"foobar","token":"azerty","list":[0,1,"2"]}
Unfortunately, list will be a mix of NSString and NSNumber. How to tell RestKit 0.2x I only want NSNumber or NSString in my NSArray?
RestKit uses KVC so surely if you just want numbers you could use the appropriate keyPath e.g. if I wanted the numbers to always be integers I could use the method integerValue which both NSNumber and NSString have:
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[MyResponse class]];
[mapping addAttributeMappingsFromDictionary:#{
#"user" : #"user",
#"token" : #"token",
#"list.integerValue" : #"list",
}];
Rather than rewriting a JSON parser to do your particular task, why not just post-process 'list' and check each object with if([list[i] isKindOfClass:[NSString class]]) and convert it to an NSNumber?

RestKit 0.20-pre3 with RKXMLReaderSerialization and XMLReader

I'm trying to use RestKit 0.20-pre3 together with RKXMLReaderSerialization and XMLReader in order to map a XML response from a WebService like this:
<ArrayOfAddressBookItem xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://tempuri.org/">
<AddressBookItem>
<CommonName>xxxxxxxxxx</CommonName>
<OU>xxxxxx</OU>
<Name>xxxxxx</Name>
<LastName>xxxxxxxxxx</LastName>
<Service>xxxxxxxxxx</Service>
<Email>xxxxxxxxxxxx</Email>
<InternalPhoneNumber>xxxxxxxxxxx</InternalPhoneNumber>
<ExternalPhoneNumber>xxxxxxxxxxx</ExternalPhoneNumber>
<Mobile>xxxxxxxxxxx</Mobile>
<Street>xxxxxxxxxxx</Street>
<PostalCode>xxxxxxxxxxx</PostalCode>
<City>xxxxxxx</City>
<County>xxxxxxxxx</County>
<SupervisorCommonName>
xxxxxxxxxxxxx
</SupervisorCommonName>
<SupervisorLastName>xxxxxxxxxx</SupervisorLastName>
</AddressBookItem>
<AddressBookItem>
<CommonName>yyyyyyyyyyyy</CommonName>
<OU>
yyyyyyyyyyyyy
</OU>
<Name>yyyyyyyyy</Name>
<LastName>yyyyyyyy</LastName>
<Service>yyyyyyyyyy</Service>
<Email>yyyyyyyyyy</Email>
<InternalPhoneNumber>yyyyyyyyy</InternalPhoneNumber>
<ExternalPhoneNumber>yyyyyyyy</ExternalPhoneNumber>
<Street>yyyyyyyyyyy</Street>
<PostalCode>yyyyyy</PostalCode>
<City>yyyyyy</City>
<County>yyyyyyyy</County>
<SupervisorCommonName>
yyyyyyyyyyy
</SupervisorCommonName>
<SupervisorLastName>yyyyyy</SupervisorLastName>
</AddressBookItem>
<AddressBookItem>
....
</AddressBookItem>
<AddressBookItem>
</ArrayOfAddressBookItem>
In the App Delegation code:
[RKMIMETypeSerialization registerClass:[RKXMLReaderSerialization class] forMIMEType:#"application/xml"];
AFHTTPClient *httpClient = [AFHTTPClient clientWithBaseURL:[NSURL URLWithString:#"http://thehostaddress/mywebserviceurl/"]];
httpClient.parameterEncoding = AFFormURLParameterEncoding;
RKObjectManager *objManager = [[RKObjectManager alloc] initWithHTTPClient:httpClient];
[objManager setAcceptHeaderWithMIMEType:RKMIMETypeTextXML];
objManager.requestSerializationMIMEType = RKMIMETypeTextXML;
RKObjectMapping *personMapping = [RKObjectMapping mappingForClass:[PersonItem class]];
[personMapping addAttributeMappingsFromDictionary:#{#"CommonName" : #"commonName", #"OU" : #"ou", #"Name" : #"name", #"LastName" : #"lastName", #"Service" : #"service", #"Email" : #"eMail", #"InternalPhoneNumber" : #"internalPhoneNumber", #"ExternalPhoneNumber" : #"externalPhoneNumber", #"Mobile" : #"mobilePhoneNumber", #"Street" : #"street", #"PostalCode" : #"postalCode", #"City" : #"city", #"County" : #"county", #"SupervisorCommonName" : #"supervisorCommonName", #"SupervisorLastName" : #"supervisorLastName"}];
RKResponseDescriptor *peopleResponse = [RKResponseDescriptor responseDescriptorWithMapping:personMapping pathPattern:#"/mywebserviceurl/" keyPath:#"ArrayOfAddressBookItem" statusCodes:[NSIndexSet indexSetWithIndex:200]];
[objManager addResponseDescriptor:peopleResponse];
later, when I want to get the objects:
[objManager getObjectsAtPath:#"/mywebserviceurl/" parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"SUCCESS: %#", mappingResult);
_items = [[mappingResult array] mutableCopy];
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"ERROR: %#", error);
}];
I can see that the mapper gets the correct number of the Array elements, but for each field of the object, I cannot retrieve the values:
2012-12-10 19:02:53.370 GenPeople2[14240:1703] T restkit.object_mapping:RKMappingOperation.m:341 Mapping attribute value keyPath 'CommonName' to 'commonName'
2012-12-10 19:02:53.370 GenPeople2[14240:1703] T restkit.object_mapping:RKMappingOperation.m:228 Found transformable value at keyPath 'CommonName'. Transforming from type '__NSDictionaryM' to 'NSString'
2012-12-10 19:02:53.371 GenPeople2[14240:1703] T restkit.object_mapping:RKMappingOperation.m:360 Skipped mapping of attribute value from keyPath 'CommonName to keyPath 'commonName' -- value is unchanged ((null))
and the result in my objects are null values.
I saw that the XML parser gives me back this:
2012-12-10 19:02:53.371 GenPeople2[14240:1703] D restkit.object_mapping:RKMapperOperation.m:218 Asked to map source
object {
City = {
text = thecity;
};
CommonName = {
text = thename;
};
County = {
text = thecounty;
};
and so on....
How to map correctly the values in order to permit RestKit to retrieve the values in the NSDictionary for each field ?
Thank Richard for your feedback, but it didn't work as I would like.
Really simpler: using nested keypaths in source object to map, worked as a charm:
RKObjectMapping *abItemMapping = [RKObjectMapping mappingForClass:[AddressBookItem class]];
[abItemMapping addAttributeMappingsFromDictionary:#{#"CommonName.text" : #"commonName", #"OU.text" : #"ou", #"Name.text" : #"name", #"LastName.text" : #"lastName", #"Service.text" : #"service", #"Email.text" : #"email"}];
I map the child nodes of an element as their own objects. So OU for example would be represented by a mapping and relationship of it's own:
RKObjectMapping *baseValueMapping = [RKObjectMapping mappingForClass:[CHRValue class]];
[baseValueMapping addAttributeMappingsFromDictionary:#{#"text" : #"value"}];
RKRelationshipMapping *ouRelation = [RKRelationshipMapping relationshipMappingFromKeyPath:#"OU" toKeyPath:#"ou" withMapping:baseValueMapping];
RKRelationshipMapping *nameRelation = [RKRelationshipMapping relationshipMappingFromKeyPath:#"Name" toKeyPath:#"name" withMapping:baseValueMapping];
[addressBookMapping addPropertyMapping:ouRelation];
[addressBookMapping addPropertyMapping:nameRelation];
Where CHRValue has a property named "value" which is an NSString. Note you need to use "text" to refer to the value of a node.

How receive Integer value in RestKit 0.20?

I've rest Object as:
#interface VFEQuestionnaireRest : NSObject
#property(copy ,nonatomic) NSNumber *iid;
#property(copy, nonatomic)NSString *namePoll;
#property(copy, nonatomic)NSSet *questions;
#end
and coming JSON as:
{"questionnaire":[
{"id":4,"namePoll":"questionario4","questions":
{"id":13,"idQuestionnaire":4,"nameQuestion":"costo?",
"questionType":{"id":2,"nameType":"WHOLE"}}}]}
The value "iid" is always "0", and namePoll have its correct value.
How can mapping int value from JSON to NSNumber objC?
-
RKObjectMapping *questionnaireMapping = [RKObjectMapping mappingForClass:[VFEQuestionnaireRest class]];
[questionnaireMapping addAttributeMappingsFromDictionary:
#{#"iid":#"id",
#"namePoll":#"namePoll"
}];
[questionnaireMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"questions" toKeyPath:#"questions" withMapping:questionMapping]];
I believe your mapping is incorrect. It should be source -> destination and not the other way around:
RKObjectMapping *questionnaireMapping = [RKObjectMapping mappingForClass:[VFEQuestionnaireRest class]];
[questionnaireMapping addAttributeMappingsFromDictionary:
#{ #"id":#"iid",
#"namePoll":#"namePoll" }];
Restkit performs int -> NSNumber conversion automatically.

Resources