RestKit mapping with parent key as attribute and key contains parenthesis - ios

Context:
// JSON
"Name_Field" : {
"param_1":"value_1",
"param_2":"value_2"
}
// Class
class Field {
name
param1
param2
}
// Mapping Functionality
[mapping addAttributeMappingFromKeyOfRepresentationToAttribute:#"name"];
[mapping addAttributeMappingsFromDictionary:#{
#"(name).param_1":#"param1",
#"(name).param_2":#"param2"
}];
Problem:
I am currently working with the above JSON / Class / Mapping code. I have been using this for a while and everything has been working as expected.
Today I have run into the scenario where the key in the JSON contains parenthesis and causes the mapping to fail. Is there a way I can get this to work?
Thanks!
Example:
"Name (haha this will break)" : {
"param_1":"value_1",
"param_2":"value_2"
}

I think RestKit gets confused because it does not know which parenthesis to use while mapping. So my guess would be to replace them with braces:
[mapping addAttributeMappingsFromDictionary:#{
#"{name}.param_1":#"param1",
#"{name}.param_2":#"param2"
}];
Let me know if this worked.

Solution:
Update the RKStringByReplacingUnderscoresWithBraces method in RKPropertyMapping to not replace the parenthesis with braces and then be sure to use braces in your attribute mappings.
// RKPropertyMapping
static NSString *RKStringByReplacingUnderscoresWithBraces(NSString *string)
{
return string;
//return [[string stringByReplacingOccurrencesOfString:#"(" withString:#"{"] stringByReplacingOccurrencesOfString:#")" withString:#"}"];
}
// Attribute Mappings
[mapping addAttributeMappingsFromDictionary:#{
#"{name}.param_1":#"param1",
#"{name}.param_2":#"param2"
}];
Explanation:
https://github.com/RestKit/RestKit/wiki/Object-mapping#handling-dynamic-nesting-attributes
In the above RestKit documentation when you are attempting to map a key to property you are instructed to use addAttributeMappingFromKeyOfRepresentationToAttribute and then map the nested keys using parentheses to denote your property followed by a '.' and your nested key.
In RestKit after it performs the map from the addAttributeMappingFromKeyOfRepresentationToAttribute attribute mapping it loops through all of your defined attribute mappings to replace your placeholder with the actual value from the key so the additional mapping operations can take place. During this process it creates a new RKPropertyMapping object and sets it's sourceKeyPath and destinationKeyPath. The property setters for these properties take the value and replace the parenthesis with braces and then RestKit does not find the incorrect mappings for the value with braces.
Example Context:
// JSON
{ "blake": {
"email": "blake#restkit.org",
"favorite_animal": "Monkey"
}
}
// Class
#interface User : NSObject
#property (nonatomic, copy) NSString* email
#property (nonatomic, copy) NSString* username;
#property (nonatomic, copy) NSString* favoriteAnimal;
#end
// Mapping
RKObjectMapping* mapping = [RKObjectMapping mappingForClass:[User class] ];
[mapping addAttributeMappingFromKeyOfRepresentationToAttribute:#"username"];
[mapping addAttributeMappingsFromDictionary:#{
#"(username).email": #"email",
#"(username).favorite_animal": #"favoriteAnimal"
}];
Example Mapping:
// Resulting Attribute Mappings
#"{username}.email": #"email",
#"{username}.favorite_animal": #"favoriteAnimal"
// 1. The attribute mapping has been performed for 'blake' > 'username'
// 2. RestKit now loops through your attribute mappings to replace '{username}' with 'blake' so your further nested attribute mappings can take place
// 3. Example: #"{username}.email": #"email"
sourceKeyPath = #"{username}.email"
destinationKeyPath = #"email"
* replaces values *
sourceKeyPath = #"blake.email"
destinationKeyPath = #"email"
* creates a new RKPropertyMapping object *
RKPropertyMapping
- sourceKeyPath = #"blake.email"
- destinationKeyPath = #"email"
Example Mapping With Parenthesis:
// JSON
{ "blake (a.k.a GOAT)": {
"email": "blake#restkit.org",
"favorite_animal": "Monkey"
}
}
// Resulting Attribute Mappings
#"{username}.email": #"email",
#"{username}.favorite_animal": #"favoriteAnimal"
// 1. The attribute mapping has been performed for 'blake (a.k.a GOAT)' > 'username'
// 2. RestKit now loops through your attribute mappings to replace '{username}' with 'blake (a.k.a GOAT)' so your further nested attribute mappings can take place
// 3. Example: #"{username}.email": #"email"
sourceKeyPath = #"{username}.email"
destinationKeyPath = #"email"
* replaces values *
sourceKeyPath = #"blake (a.k.a GOAT).email"
destinationKeyPath = #"email"
* creates a new RKPropertyMapping object *
RKPropertyMapping
- sourceKeyPath = #"blake {a.k.a GOAT}.email" // DOES NOT MATCH
- destinationKeyPath = #"email"

Related

RestKit NSDictionary mapping NULL values

I have a mapped object which have this property:
#interface SynchObj : NSObject
#property (nonatomic, copy) NSDictionary *fields;
#end
mapped as
mappingDict = #{ #"fields" :#"fields",};
responseMapping = [RKObjectMapping mappingForClass:[SynchObj class]];
[responseMapping addAttributeMappingsFromDictionary:mappingDict];
But when the server send a dictionary like this
{
name:null
surname:null
}
the mapping produces a dictionary with these values:
name : "<null>"
surname : "<null>"
I would like to have "" instead of "". Is it possible?
You should be able to use KVC validation which RestKit supports to verify / edit the value before it is set. If you implement validateValue:forKey:error: on your destination class (SynchObj) you can verify and edit the value being set for any key (you would need to iterate the keys and values of the dictionary).

Restkit dynamic mapping based on previous value

I'm dealing with the following json:
{
"status":
{
"errorCode":{errCode},
"errorMsg":{errMsg},
},
"data":
{
"key1":"value1",
"key2":"value2",
"key3":"value3",
}
}
I need to use different mapping for the object in data, according to errCode value. I tried to use RKDynamicMapping, but got confused with the keyPaths..
Is there any way to achieve that?
Edit:
I'm using this code:
RKObjectMapping *infoMapping = [RKObjectMapping mappingForClass:[Info class]];
[infoMapping addAttributeMappingsFromDictionary:#{#"data.key1":#"key1", #"data.key2":#"key2", #"data.key3:#"key3}];
RKDynamicMapping *dynamicMapping = [RKDynamicMapping new];
[dynamicMapping addMatcher:[RKObjectMappingMatcher matcherWithKeyPath:#"status.errorCode" expectedValue:0 objectMapping:infoMapping]];
[dynamicActivateTravelMapping setObjectMappingForRepresentationBlock:^RKObjectMapping *(id representation) {
NSNumber *errorCode = [[representation valueForKey:#"status"] valueForKey:#"errorCode"];
if ([errorCode integerValue] == WSErrorUnknown) {
return unknownMapping;
}
else{
return infoMapping;
}
}];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:dynamicMapping method:RKRequestMethodPOST pathPattern:kResource keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptor:responseDescriptor];
Well, basically this code works, but I have couple of issues with it:
It seems very ugly to keep writing "data.x" for each attribute of the mapping.
The result dictionary comes back with NSNull as the key for the "data" mapping. (its value is fine though..)
status = "<ServerStatusCode: 0x155b2fa0>";
"<null>" = "Info: 0x15535800>";
Technically when you create the dynamic mapping it should be:
[dynamicMapping addMatcher:[RKObjectMappingMatcher matcherWithKeyPath:#"status.errorCode" expectedValue:#0 objectMapping:infoMapping]];
Note the #0, because you should be passing an object (NSNumber), not a plain number (where 0 will equate to nil and another number will cause problems).
The other 2 complaints you have are just the way things are. The first is because you need to index into the data. Both are caused by the keyPath:nil, specifying that the data is accessed from the top level and that there is no key with which to access the result.

RestKit: How to POST a NSManagedObject as JSON without any nesting attributes?

This should be a really easy one, but sadly I haven't found any answer...
What RestKit mapped my object to:
request.body={"user":{"pass":"1234","id":0,"login":"awesome_guy","tier":0}}
What I really want:
{"pass":"1234","id":0,"login":"awesome_guy","tier":0}
Just without the object name "user".
If you have dealt with this issue, it'll take you 5 seconds to answer. If you haven't used RestKit. You do not know the answer. I'm attaching my code anyway:
User Object Mapping:
/* ===========================
* ====User Object Mapping====
* ==========================*/
RKEntityMapping* userObjectMapping = [RKEntityMapping mappingForEntityForName:NSStringFromClass([User class]) inManagedObjectStore:objectManager.managedObjectStore];
NSDictionary *userObjectMappingDict = #{
#"id":#"id",
#"login":#"login",
#"firstName":#"firstName",
#"lastName":#"lastName",
#"phoneNumber":#"phoneNumber",
#"email":#"email",
#"tier":#"tier",
#"sessionId":#"sessionId",
#"pass":#"password"
};
userObjectMapping.identificationAttributes = #[#"id"];
[userObjectMapping addAttributeMappingsFromDictionary:userObjectMappingDict];
RKEntityMapping* userBusinessMapping = [RKEntityMapping mappingForEntityForName:#"Business" inManagedObjectStore:objectManager.managedObjectStore];
[userBusinessMapping addAttributeMappingsFromDictionary:#{#"business":#"id"}]; // Nil Key path
[userObjectMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:nil toKeyPath:#"business" withMapping:userBusinessMapping]];
You should have an instance of RKRequestDescriptor that you haven't shown. It's created with requestDescriptorWithMapping:objectClass:rootKeyPath:. You have the root key path set to #"user" and you should set it to nil.

Reskit - Mapping to array

I'm trying to run some unit tests to test my mappings with RestKit v0.20, however I am getting an error that my destination object is nil. I have track this down to the fact that the mapping is failing because the sourceType is an NSArray and my destinationType is an NSNumber. I think this is because my mapping keypaths are incorrect. I am trying to map the songCard JSON to my objet. I have included my JSON and mapping test below.
It Would be great it someone could help me to set the correct keypath.
{"status" : 2000,
"content" : {
"cardList" : [
{
"songCard" : {
"likes" : 2,
"dislikes" : 3
}
}
]
},
"message" : "OK"
}
Unit Test class
- (RKObjectMapping *)songMetadataMapping
{
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[SongMetadata class]];
[mapping addAttributeMappingsFromDictionary:#{
#"content.cardList.songCard.likes": #"likes"
}];
return mapping;
}
- (void)testSongMetadataMapping
{
NSString *parsedJSON = [RKTestFixture parsedObjectWithContentsOfFixture:#"songMetadata.json"];
RKMappingTest *test = [RKMappingTest testForMapping:[self songMetadataMapping] sourceObject:parsedJSON destinationObject:nil];
[test addExpectation:[RKPropertyMappingTestExpectation expectationWithSourceKeyPath:#"content.cardList.songCard.likes" destinationKeyPath:#"likes" value:#"2"]];
STAssertTrue([test evaluate], #"Mappings failed");
}
UPDATE
After further debugging I have found that the value 2 in my JSON string is being evaluated as an NSArray, when this should be evaluated as NSNumber. As a quick test I removed the [ ] in my JSON and the value 2 was correctly evaluated as an NSNumber. This doesn't solve my problem though as I have need to identify my JSON as an array of songCard objects
As you have noticed, you cannot use the keypath as you have specified when an array is in play. I can think of two options - the first is a long shot, but does the key path content.cardList[0].songCard.likes work?
Otherwise, consider using the method:
+ (instancetype)expectationWithSourceKeyPath:(NSString *)sourceKeyPath
destinationKeyPath:(NSString *)destinationKeyPath
evaluationBlock:(RKMappingTestExpectationEvaluationBlock)evaluationBlock;
With keypath content.cardList and supplying an evaluation block that 1) checks that the mapping is an array that contains a single object. You can then check that the object contains a songCard object and that has a likes value of 2.

RestKit primary key attribute

I load data from a json file, I save it.
I do it twice ...
I got two entries in my Core Data sqlite database.
Even if I set in the mapping the primaryKeyAttribute.
mapping.primaryKeyAttribute = #"code";
[mapping mapAttributesFromArray :mappedFields];
[[RKObjectManager sharedManager].mappingProvider setMapping:mapping forKeyPath:entityName];
My Json
{ "MyEntity": [ { "code" : "axv2","data" : "content"}]};
Here the callback :
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects {
NSLog(#"Entries loaded %d",[objects count]);
lastResult = objects;
for(MyEntity * myEntity in lastResult) {
[self saveContext];
}
}
My entity is correctly mapped ... But Restkit allow one to save duplicate entries with the same primary key?
It's weird, I understood that this primary key attribute would avoid this problem.
No, that is not the case, as Core Data keeps its own keys. You can easily solve this problem by checking if your primary key exists and before saving the entity instance in question.
As of the latest RESTKit version (0.23.2) you can define the primary key like this:
[_mapping addAttributeMappingsFromDictionary:#{ #"id" : #"objectId", #"name" : #"name" }];
[_mapping setIdentificationAttributes:#[ #"objectId" ]];
Whereas objectId is you primary key on the core data object.

Resources