Reskit - Mapping to array - ios

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.

Related

RestKit mapping with parent key as attribute and key contains parenthesis

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"

RestKit - saving JSON content into a string coredata attribute

I need to parse a JSON web service response containing a key whose children are not known.
For example let's have the following JSON response where the keys of the customData attribute are defined at runtime:
{
"score": 996,
"customData": { "key1": "key1value", "key2": "key2value" },
"isRegistered": true,
"allowOpening": "OK"
}
Would it be possible to save the JSON content of customData into a string coredata attribute?
I've tried with a simple mapping like this:
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:[[self class] description] inManagedObjectStore:managedObjectStore];
[mapping addAttributeMappingsFromDictionary:#{
#"score": #"score",
#"customData":#"customData",
#"isRegistered": #"isRegistered",
#"allowOpening": #"allowOpening"}];
but it doesn't work, customData saved by coredata is always empty.
Many thanks,
DAN
Would it be possible to save the JSON content of customData into a string coredata attribute?
No, because it will be deserialised to a dictionary and there is no converter for that to a string.
You can store it as a dictionary. It you could add a relationship mapping with a dynamic mapping which checks what the keys are and defines the mapping on the fly...

RestKit: Ignoring some dynamic nesting attributes

I receive JSON objects like this:
{
"rent": {
"id": "someId"
},
"upcoming": {
"id": "someId"
},
"watchnow": {
"id": "someId"
}
}
I then set forceCollectionMapping to YES on my mapping to get one object for each key, i.e. one object for "rent", one for "upcoming" and one for "watchnow". Specifically this is done with this code:
[searchResultsMapping addAttributeMappingFromKeyOfRepresentationToAttribute:#"searchSection"];
So this succesfully gives me three objects for which I can then do some relationship mapping to get the id keys and what ever else is on the object.
Now, my problem is that if an error occurs, I get this JSON code:
{
"error": {
"errorcode": "someId"
}
}
So (searchSection) becomes "error" and my relationship mapping looks for "id" but it's not there so the mapping fails. The problem is that setting addAttributeMappingFromKeyOfRepresentationToAttribute makes RestKit try to make an object from every single key, and I can't expect every key to be relevant and useful for my mappings. Can I do anything about this?
You have a couple of options:
Use an RKDynamicMapping as the root mapping for your response descriptor
Use multiple response descriptors to specify exactly which keypaths to process
Use KVC validation to reject the error mapping (not ideal as the error isn't really captured)
For the dynamic mapping option, the dynamic mapping has the forceCollectionMapping option set and it checks the top level key available and returns the appropriate mapping (which wouldn't have forceCollectionMapping set and which uses addAttributeMappingFromKeyOfRepresentationToAttribute:).
I got it to work using Wain's first suggestion. Here's the solution if anyone has the same issue:
I created a dynamic mapping like this:
RKDynamicMapping *dynamicMapping = [RKDynamicMapping new];
dynamicMapping.forceCollectionMapping = YES;
[dynamicMapping setObjectMappingForRepresentationBlock:^RKObjectMapping * (id representation) {
if ([representation valueForKey:#"watchnow"] || [representation valueForKey:#"upcoming"] || [representation valueForKey:#"rent"]) {
return searchResultsMapping;
}
return nil;
}];
As you can see in my example at the top, I'm only interested in keys named "watchnow", "upcoming" or "rent".
The searchResultsMapping which is returned is configured like this:
RKObjectMapping *searchResultsMapping = [RKObjectMapping mappingForClass:[TDXSearchResults class]];
[searchResultsMapping addAttributeMappingFromKeyOfRepresentationToAttribute:#"searchSection"];
So I now end up with three SearchResult objects with either "watchnow", "upcoming" or "rent" in their searchSection NSString property.

RestKit: Map nested Array to Object

I have two classes to map a JSON response: Item and FrequentProps
Item has the following properties:
frequentProps, identifier, name
FrequentProps has the properties
propOne
propTwo
propThree
propFour
You can see that frequentProps in Item is of type FrequentProps.
Consider the following JSON Response:
[
{
"frequentProps": [
{
"propOne": 174
},
{
"propTwo": 9.726
},
{
"propThree": 2.021
},
{
"propFour": 25.07
}
],
"identifier": "4223",
"name": "TheName"
}
]
The outer part of the JSON is supposed to be mapped to an object of class Item, the nested Array is supposed to be mapped to frequentProps, as a property of the object. Unfortunately, frequentProps is not mapped to the Items property with the same name but into an NSArray (if I define the type of the property as NSArray, otherwise the property remains nil).
Here's the configuration:
RKObjectMapping *itemMapping = [RKObjectMapping mappingForClass:[Item class]];
[item addAttributeMappingsFromDictionary:[Item attributesMapping]];
RKObjectMapping *frequentPropsMapping = [RKObjectMapping mappingForClass:[FrequentProps class]];
[frequentPropsMapping addAttributeMappingsFromDictionary:[FrequentProps attributesMapping]];
[itemMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"frequentProps"
toKeyPath:#"frequentProps"
withMapping:frequentProps]];
// adding the response descriptor, etc...
How can I map the frequentProps directly into an object of type FrequentProps, which remains a property of Item?
You can't, because there is no way in the mapping to specify that you are indexing into an array and putting that index into a specified key. I expect that this will never be supported.
Not ideal but: What you could do it to add the array property as well, with a custom setter method. When the setter is called, mutate the data by creating an instance of FrequentProps and setting the properties from the array contents.

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