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.
Related
I'm trying to figure out how to realize a mapping of an API response into CoreData objects using RestKit. The API uses JSOG standard. Here is an example:
[
{
"#id": "1",
"name": "Sally",
"friend": {
"#id": "2",
"name": "Bob",
"friend": {
"#id": "3",
"name": "Fred",
"friend": { "#ref": "1" }
}
}
},
{ "#ref": "2" },
{ "#ref": "3" }
]
How would I create an RKEntityMapping for such a JSON? Mapping of simple attributes is trivial, the question is how to setup the relationships here so they work with #ref, also, when the top level user object contains the #ref only.
RKEntityMapping *userMapping = [RKEntityMapping mappingForEntityForName:#"Question"
inManagedObjectStore:[RKObjectManager sharedManager].managedObjectStore];
[userMapping addAttributeMappingsFromDictionary:#
{
#"#id" : #"identifier",
#"name" : #"name"
}];
[userMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"friend"
toKeyPath:#"friend"
withMapping:userMapping]];
My guess is that i could use code below to handle the #ref inside of an object:
[userMapping addConnectionForRelationship:#"friend" connectedBy:#{#"#ref": #"#id"}];
but is it correct?
How would I map either a full object or just a reference to an already-provided object (via #ref) to actual Core Data objects?
How can I map the top elements of the JSON (being a list of User objects) to an actual list of User entities?
Add a relationship using the mapping itself, so it drills recursively. Use foreign key mapping for the refs.
Looking at this again following your comment it may actually be a good candidate for a dynamic mapping instead of foreign key. This would mean creating a new mapping which just uses #"#ref" : #"identifier" for the connections and a dynamic mapping which looks at the keys and decides wether to use this connection mapping or the full userMapping.
Note that both of these mappings need to specify that identifier is the unique key of the object and you should use a memory cache to allow the existing objects to be found instead of creating duplicates.
Trying to connect two entities from my API from a single response.
{
"id": 4546,
"body": "Direct message",
"status": "received",
"from": {
"id": 723,
"signature": "Mr. Whatever"
},
"sent_at": "2014-06-05T21:33:15Z",
"sent_to": ...
}
The object is a Message entity, the from field is a nested Sender entity. Here is the relevant relationship mapping:
RKEntityMapping *senderMapping = [RKEntityMapping mappingForEntityForName:#"Sender"
inManagedObjectStore:[AMModelManager sharedManager].managedObjectStore];
senderMapping.identificationAttributes = #[ #"remoteId" ];
[senderMapping addAttributeMappingsFromDictionary:#{
#"id" : #"remoteId",
#"signature" : #"signature"
}];
[messageMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"from"
toKeyPath:#"sender"
withMapping:senderMapping]];
The first time the objects are mapped, the relationship is not established. I see it being mapped in the log, but when I print any particular Sender object, its messages inverse relationship shows an empty array instead of a relationship fault.
Mapped relationship object from keyPath 'from' to 'sender'. Value: <Sender: 0x16a10990> (entity: Sender; id: 0x16a14580 <x-coredata:///Sender/t16539BDF-5606-442B-9EE2-D88363FB04AD29> ; data: {
messages = (
);
remoteId = 723;
signature = "Mr. Whatever";
})
This makes the fetch requests I have in my application fail when I want to search for all messages from a given sender.
The next time I run the app and the mapping takes place again, all Message and Sender entities are remapped, the relationship is connected as expected, and my fetch requests behave as expected.
I really have no idea why the relationship isn't valid on the initial load. I don't know if it's a Core Data config issue or a RestKit issue, but it's driving me crazy and I would love any additional insight available. Glad to provide more information if necessary.
Edit
This is different than the proposed duplicate because this is a to-one relationship, so the proposed answer of using RKAssignmentPolicyUnion is inapplicable.
Update
Here is the fetch request I'm using for the controller I'm listening to:
NSFetchRequest *fetch = [NSFetchRequest fetchRequestWithEntityName:#"Sender"];
fetch.predicate = [NSPredicate predicateWithFormat:#"SUBQUERY(SELF.messages, $message, $message.direct == %#).#count > 0 && SELF.remoteId != %#", #(YES), user.remoteId];
fetch.sortDescriptors = #[
[NSSortDescriptor sortDescriptorWithKey:#"signature" ascending:YES selector:#selector(caseInsensitiveCompare:)]
];
Semantically I'm trying to say "Give me all the Senders that have sent a Message directly to the current user, and who are not the current user, sorted by their signature."
This fetch request fetches the Sender objects that I expect when I run it after my data is retrieved, but the controller fires no updates and my table remains empty until the next time I run the application.
K, another classic case of "You did it wrong and you were looking in all the wrong places to fix it."
I'd overridden the didChangeValueForKey: method on my Message entity's implementation to monitor some transient state, and neglected to call [super didChangeValueForKey:key], which fubar'd the inverse connection assignments in the mapping. Absolutely no issue with Core Data or RestKit. Just me being careless.
I am using RestKit to Post an object to the server.
The object contains an NSArray, which I convert to NSDictionaries before adding it to the parameters list (an NSDictionary)
the NSDictionary queryParams looks like this (printed out using NSLog, and removing some fields for clarity)
requestParams = {
"app_version" = "v1.0(1.006)";
"extended_information" = {
"travel_locations" = (
{
"Aircard_or_Tablet" = 0;
Country = "United States";
"End_Date" = "04/12/2013";
"Start_Date" = "03/12/2013";
}
);
"using_voice" = 0;
};
}
I then send it:
[objectManager postObject:nil path:server_path parameters:requestParams success:… failure:…]
The problem is that when RestKit creates the query string, it seems to increment the counter for each key in the dictionary, rather than per item in the array. The following is what is received by the server. I added a line break after each parameter for clarity:
app_version=v1.0(1.006)
&extended_information[travel_locations][0][Aircard_or_Tablet]=0
&extended_information[travel_locations][1][Country]=United States
&extended_information[travel_locations][2][End_Date]=04/12/2013
&extended_information[travel_locations][3][Start_Date]=03/12/2013
&extended_information[using_data]=0
The server is expecting (and I would expect) travel_locations to remain at [0] for all the keys in that one object, like this:
app_version=v1.0(1.006)
&extended_information[travel_locations][0][Aircard_or_Tablet]=0
&extended_information[travel_locations][0][Country]=United States
&extended_information[travel_locations][0][End_Date]=04/12/2013
&extended_information[travel_locations][0][Start_Date]=03/12/2013
&extended_information[using_data]=0
And, of course, if a second travel_location were there it would have [1], the third would have [2], and so forth.
Is this a bug in RestKit? Is there a better way I can be sending this request so that it does work? I do not have the option of changing the server.
To question is:
How can I have RestKit convert the NSDictionary for parameters properly so that it does not increment the array index identifier for each key in the array object?
Thanks,
-Nico
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.
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.