RestKit - Hydrate array of foreign keys - objective-c

I have the following JSON:
{
"users": [
{"id": "1", "name": "John Doe"},
{"id": "2", "name": "Bill Nye"}
],
"groups": [
{"id": "1", "name": "Group1", "users": ["1", "2"]},
{"id": "2", "name": "Group2", "users": ["1"]}
]
}
...and a Core Data model with User and Group objects. The group object has a to-many relationship (NSSet) to users.
I have found the following thread that seems to indicate that this is possible, but contains no explanation of how such a mapping is to be performed:
https://github.com/RestKit/RestKit/issues/284
How do I perform this mapping such that each Group's "users" relationship is properly connected?
Note: I have mappings set up that correctly map the JSON users and groups to their respective Core Data objects. However, each group's "users" NSSet is empty.

So, I figured this out using RestKit 0.20(pre2).
JSON needed to be changed to the following (note the attribute names in the group's users array):
{
"users": [
{"id": "1", "name": "John Doe"},
{"id": "2", "name": "Bill Nye"}
],
"groups": [
{"id": "1", "name": "Group1", "users": [{"id" : "1"}, {"id" : "2"}]},
{"id": "2", "name": "Group2", "users": [{"id" : "1"}]}
]
}
Then, the following mappings:
RKEntityMapping *userMapping = [RKEntityMapping mappingForEntityForName:#"User" inManagedObjectStore:managedObjectStore];
userMapping.identificationAttributes = #[#"id"];
[userMapping addAttributeMappingsFromArray:#[#"id", #"name"]];
RKEntityMapping *groupMapping = [RKEntityMapping mappingForEntityForName:#"Group" inManagedObjectStore:managedObjectStore];
groupMapping.identificationAttributes = #[#"id"];
[groupMapping addAttributeMappingsFromArray:#[#"id", #"name"]];
[groupMapping addRelationshipMappingWithSourceKeyPath:#"users" mapping:userMapping];
And finally, the following responseDescriptors:
RKResponseDescriptor *userResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:classMapping pathPattern:#"/api/allthejson" keyPath:#"users" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
RKResponseDescriptor *groupResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:classMapping pathPattern:#"/api/allthejson" keyPath:#"groups" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptorsArray:#[userResponseDescriptor, groupResponseDescriptor]];
Then get your objects using RKObjectManager's getObjectsAtPath:parameters:success:failure method and your done!

RestKit has many issues especially when it comes to modeling relationships. Debugging the mappings can be daunting.
Here is some code that deals with what you describe without RestKit.
NSArray *userArray;
// an array populated with NSManagedObjects
// correctly converted from JSON to the User entity
NSArray *groups = [jsonObject objectForKey:#"groups"];
for (NSDictionary *d in groups) {
Group *g = [NSEntityDescription insertNewObjectForEntityForName:#"Group"
inManagedObjectContext:_managedObjectContext];
g.id = #([d[#"id"] intValue]);
g.name = d[#"name"];
NSArray *users = d[#"users"];
for (NSString *s in users) {
User *u = [[userArray filteredArrayUsingPredicate:
[NSPredicate predicateWithFormat:#"id = %#", #([s intValue])]]
objectAtIndex:0];
[g addUsersObject:u];
}
}
// save

Related

restkit nested object with array mapping

I have this json:
{
"status": "ok",
"post": {
"id": 171,
"type": "locations",
"slug": "plaza-404",
"url": "http://localhost/?locations=plaza-404",
"status": "publish",
"title": "Sucursal Plaza 404",
"title_plain": "Sucursal Plaza 404",
"content": "",
"excerpt": "",
"date": "2014-03-05 19:59:50",
"modified": "2014-03-07 19:11:43",
"categories": [],
"tags": [],
"author": {
"id": 1,
"slug": "arturocalvo",
"name": "arturocalvo",
"first_name": "",
"last_name": "",
"nickname": "arturocalvo",
"url": "",
"description": ""
},
"comments": [],
"attachments": [],
"comment_count": 0,
"comment_status": "closed",
"custom_fields": {
"id": [
"1"
],
"location_id": [
"1"
],
"calle": [
"Av. Gomez Morin No.404 Local A3-5"
]
}
And i Want to map only the title the custom field called "calle", this is my interface for Location
#interface Location : NSObject
#property (nonatomic, copy) NSString * name;
#property (nonatomic, copy) NSSet * calle;
#end
and this is my code
RKObjectMapping *locationMapping = [RKObjectMapping mappingForClass:[Location class]];
RKObjectMapping *locationCustomMapping = [RKObjectMapping mappingForClass: [LocationCustom class]];
[locationMapping addAttributeMappingsFromDictionary:#{
#"title": #"name",
#"calle": #"calle"
}];
RKResponseDescriptor *locationResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:locationMapping method:RKRequestMethodAny pathPattern:nil keyPath:#"post" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptor:locationResponseDescriptor];
NSURLRequest *request = [NSURLRequest requestWithURL:baseURL];
RKObjectRequestOperation *objectRequestOperation = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:#[ locationResponseDescriptor ]];
But for some reason i cant map it, everythime gets a null value, can somebody help me?
You are missing a level in the JSON that you need to tell RestKit to navigate. Use:
#"custom_fields.calle": #"calle"
in your mapping.

dynamic object mapping for nested json in Restkit 0.20

Can anyone please help me map this json object in restkit 0.20 ?
"feeds" : [
{
"id": 32131354,
"caption": "Blah Blah Blah.",
"story" : "blah blah someone blah blah someone else",
"story_tags": {
"0": [
{
"id": 21231654,
"name": "Someone",
"offset": 0,
"length": 25,
"type": "page"
}
],
"33": [
{
"id": 3213212313,
"name": "Someone else",
"offset": 33,
"length": 12,
"type": "page"
}
]
},
}
{
...
}
]
I would really appreciate any help. This is what am doing right now:
RKEntityMapping *feedsMapping = [RKEntityMapping mappingForEntityForName:NSStringFromClass([FacebookFeed class]) inManagedObjectStore:managedObjectStore];
feedsMapping.identificationAttributes = #[#"id"];
[feedsMapping addAttributeMappingsFromDictionary:#{
#"caption" : #"caption",
#"id" : #"id",
#"story" : #"story",
}];
and this is for relationship mapping
RKEntityMapping *facebookFeedStoryTagsMapping = [RKEntityMapping mappingForEntityForName:NSStringFromClass([FacebookFeedStoryTag class]) inManagedObjectStore:managedObjectStore];
facebookFeedStoryTagsMapping.identificationAttributes = #[#"id"];
[facebookFeedStoryTagsMapping addAttributeMappingToKeyOfRepresentationFromAttribute:#"offset"];
[facebookFeedStoryTagsMapping addAttributeMappingsFromDictionary:#{
#"(offset).id" : #"id",
#"(offset).length" : #"length",
#"(offset).name" : #"name",
#"(offset).type" : #"type",
}];
[feedsMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"story_tags" toKeyPath:#"story_tags" withMapping:facebookFeedStoryTagsMapping]];
but i am getting the following error:
restkit.object_mapping:RKMappingOperation.m:745 Key path 'story_tags' yielded collection containing another collection rather than a collection of objects: (
(
{
id = 31314654654;
length = 25;
name = "Someone";
offset = 0;
type = page;
}
)
)
The JSON "0": [ presents an issue because we really want a dictionary in there (that is how your mapping is written), but we actually have an array. This is the source and meaning of the error you see.
Ideally you would change the JSON...
As it stands your FacebookFeedStoryTag would need to map the entire array (and the dictionary it contains) into a transformable attribute. This is because the mapping can not handle the nested array.
You could make this transformable attribute a transient and implement a custom setter / willSave method to take the array and extract the dictionary contents. As the key names match this could be simply done using setValuesForKeysWithDictionary: (though you would need to handle the offset key).

Restkit 0.20 Dynamic Nested Mapping

I am using Restkit 0.20 to map and store objects in core data. For basic mappings it works well, but it doesnt work for nested dynamic mappings. This is my example response:
[
{
"actor": {
"id": 7,
"first_name": "Murat",
"last_name": "Akbal"
},
"target": {
"content_type": "dress",
"type": {
"id": 1,
"name": "leggings",
"kind": "bottom"
},
"style": {
"id": 1,
"name": "sport"
},
"full_name": "Murat - sport leggings",
"id": 19,
}
},
{
"actor": {
"id": 7,
"first_name": "Murat",
"last_name": "Akbal"
},
"target": {
"content_type": "wardrobe",
"style": {
"id": 1,
"name": "sport"
},
"id": 38,
"season": "spring",
"name": "asdasd"
}
}
]
I use following entity mapping to map response:
//create entity mapping
RKEntityMapping *objectMapping = [RKEntityMapping mappingForEntityForName:#"Action"
inManagedObjectStore:[RKObjectManager sharedManager].managedObjectStore];
//create dynamic mapping
RKDynamicMapping *dynamicMapping = [RKDynamicMapping new];
//dressMapping and wardrobeMapping predefined mappings, they work well when mapping the wardobe and dress alone.
[dynamicMapping setObjectMappingForRepresentationBlock:^RKObjectMapping *(id representation) {
if ([[representation valueForKey:#"content_type"] isEqualToString:#"wardrobe"]) {
return wardrobeMapping;
} else if ([[representation valueForKey:#"content_type"] isEqualToString:#"dress"]) {
return dressMapping;
}
return nil;
}];
[objectMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"target" toKeyPath:#"target" withMapping:dynamicMapping]];
//maps actor
[objectMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"actor" toKeyPath:#"actor" withMapping:actorMapping]];
And lastly, I register the objectMapping as response descriptor:
RKResponseDescriptor *responseDescriptor =
[RKResponseDescriptor responseDescriptorWithMapping:objectMapping
pathPattern:nil
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[[RKObjectManager sharedManager] addResponseDescriptor:responseDescriptor];
This doesn't work for my example response, gives the following error:
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<Wardrobe 0x110e1a90> valueForUndefinedKey:]: the entity Wardrobe is not key value coding-compliant for the key "type".'
It asks "type" key which does not exist in wardrobe, but exists for dress. Somehow, it tries to map wardrobe with dress mapping. Do you have any idea about that issue?
fyi, type of target in coredata is transformable.
I found the problem. Actually it mapped response successfully, but when deleteLocalObjectsMissingFromMappingResult is preforming it throws an exception from this line:
id managedObjects = event.keyPath ? [objectsAtRoot valueForKeyPath:event.keyPath] : objectsAtRoot;
it seeks keyPath for all object at mapping results instead of each individual object. So in my situation it cannot find the "type" key for wardrobe because it does not exist. As a basic solution, I surrounded the code block with try-catch

Mapping With Restkit

I am mapping with simple json response,
{
"town": "ABC",
"website": "http://xyz.com",
"fee": "50.00",
"event_id": 32,
"images": [
{
"large": "1.jpg",
"thumbnail": "2.jpg"
},
{
"large": "1.jpg",
"thumbnail": "2.jpg"
},
{
"large": "1.jpg",
"thumbnail": "2.jpg"
}
]
}
and my mapping function look like this
RKManagedObjectMapping *eventMapping = [RKManagedObjectMapping mappingForClass:[Events class] inManagedObjectStore:manager.objectStore];
[eventMapping mapKeyPath:#"town" toAttribute:#"town"];
[eventMapping mapKeyPath:#"website" toAttribute:#"website"];
[eventMapping mapKeyPath:#"fee" toAttribute:#"fee"];
[eventMapping mapKeyPath:#"event_id" toAttribute:#"event_id"];
eventMapping.primaryKeyAttribute = #"event_id";
[manager.mappingProvider setMapping:eventsMapping forKeyPath:#""];
RKManagedObjectMapping *imagesMapping = [RKManagedObjectMapping mappingForClass:[Images class] inManagedObjectStore:manager.objectStore];
[imagesMapping mapKeyPath:#"large" toAttribute:#"large"];
[imagesMapping mapKeyPath:#"thumbnail" toAttribute:#"thumbnail"];
[eventsMapping mapKeyPath:#"images" toRelationship:#"images" withMapping:imagesMapping];
But i am not to make a relationship successfully. My Db is always showing empty relationship b/w images and events. i tried connectrelationship, but i think i need primary key for images table to make a connection. can anyone guide me to correct way of doing this.
Do i need to add primary key for Images table or is it possible to access the 'event_id' from parent table (event) and use it for making relations.
Try this
//Note this is Restkit 0.2
[eventMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"images" toKeyPath:#"images" withMapping: imagesMapping]];

Restkit mapping POST Response

I am trying to map a POST response via the code below, but when the didLoadObjects is reached Restkit is trying to map the same object as I am trying to POST (POSTing a Foo and want it to map Bar, but RestKit is trying to map the return to Foo).
[self.objectManager sendObject:mySyncInstance toResourcePath:url usingBlock:^(RKObjectLoader* postLoader) {
postLoader.delegate = aDelegate;
postLoader.objectMapping = [self.objectManager.mappingProvider objectMappingForClass:[Bar class]];
postLoader.method = RKRequestMethodPOST;
postLoader.userData = kUserDefaultsAttendanceReads;
postLoader.serializationMIMEType = RKMIMETypeJSON;
postLoader.serializationMapping
[postLoader setUsername:[prefs objectForKey:kCurrentUsername]];
[postLoader setPassword:[prefs objectForKey:kCurrentPassword]];
}];// end sendObject
The solution was to set rootKeyPath.
myPostReturnMapping.rootKeyPath = #"persons";
"persons": [
{ "id": "1",
"firstname": "hans",
"lastname": "maier",
"genre": "m"
},
{ "id": "2",
"firstname": "michael",
"lastname": "schmidt",
"genre": "w"
}]