I have the following JSON structure which I want to map with RestKit in CoreData:
[
{
"id": 1,
"type": "folder",
"name": "Folder 1",
"children": [
{
"id": 1,
"type": "document",
"name": "Document 1"
},
{
"id": 2,
"type": "folder",
"name": "Folder 2",
"children": [
{
"id" : 2,
"type": "document",
"name": "Document 2"
}
]
}
]
}
]
It's a typical filesystem structure where a folder can contain other folders or documents.
I have two CoreData entities: Folder and Document.
The Folder entity has the following relationships:
Here is my Mapping Code:
RKEntityMapping* foldersMapping = [RKEntityMapping mappingForEntityForName:#"Folder" inManagedObjectStore:managedObjectStore];
foldersMapping.identificationAttributes = #[ #"objectId" ];
[foldersMapping addAttributeMappingsFromDictionary:#{#"id" : #"objectId",
#"name" : #"name"}];
RKEntityMapping* documentsMapping = [RKEntityMapping mappingForEntityForName:#"Document" inManagedObjectStore:managedObjectStore];
documentsMapping.identificationAttributes = #[ #"objectId" ];
[documentsMapping addAttributeMappingsFromDictionary:#{#"id" : #"objectId",
#"name" : #"name"}];
In my dynamic mapping I use the type of the object to decide if it is a document or a folder.
RKDynamicMapping* dynamicMapping = [RKDynamicMapping new];
[dynamicMapping setObjectMappingForRepresentationBlock:^RKObjectMapping *(id representation) {
if ([[representation valueForKey:#"type"] isEqualToString:#"Folder"]) {
return foldersMapping;
} else if ([[representation valueForKey:#"type"] isEqualToString:#"Document"]) {
return documentsMapping;
}
return nil;
}];
Now I added the dynamic mapping as relationship mapping to the folder mapping for the property children two times.
[foldersMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"children" toKeyPath:#"children" withMapping:dynamicMapping]];
[foldersMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"children" toKeyPath:#"documents" withMapping:dynamicMapping]];
And here is my Problem: All this works, but I got the following Warnings:
RestKitNesterFolderTestProject[9969:3a03] W
restkit.object_mapping:RKMappingOperation.m:554 WARNING: Failed
mapping nested object: The operation couldn’t be completed. (Cocoa
error 1550.)
I think the Problem is that I doubled the mapping for the relationship children, so RestKit tries to map each child two times, once as Folder and once as Document.
Any help is appreciated.
Related
The API need to pass an array in an url query parameter, how to acheive this in iOS?
I only know how to pass a single vaue, like the API below : ?title=Design Milk&id=feed/http://feeds.feedburner.com/design-milk
API sample:
"title": "Design Milk",
"id": "feed/http://feeds.feedburner.com/design-milk",
"categories": [
{
"id": "user/c805fcbf-3acf-4302-a97e-d82f9d7c897f/category/design",
"label": "design"
},
{
"id": "user/c805fcbf-3acf-4302-a97e-d82f9d7c897f/category/weekly",
"label": "weekly"
},
{
"id": "user/c805fcbf-3acf-4302-a97e-d82f9d7c897f/category/global.must",
"label": "must reads"
}
]
Create a collection and then use NSJSONSerialization to create JSON data representation. Use the resulting data as the POST data.
NSDictionary *parameters = #{
#"title": #"Design Milk",
#"id": #"feed/http://feeds.feedburner.com/design-milk",
#"categories": #[
#{
#"id": #"user/c805fcbf-3acf-4302-a97e-d82f9d7c897f/category/design",
#"label": #"design"
},
#{
#"id": #"user/c805fcbf-3acf-4302-a97e-d82f9d7c897f/category/weekly",
#"label": #"weekly"
},
#{
#"id": #"user/c805fcbf-3acf-4302-a97e-d82f9d7c897f/category/global.must",
#"label": #"must reads"
}
]
};
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dictData options:0 error:&error];
I am using RestKit to map the following JSON to core data.
This is my model:
this is my methods:
RKEntityMapping *userMapping = [RKEntityMapping mappingForEntityForName:NSStringFromClass([User class]) inManagedObjectStore:managedObjectStore];
userMapping.identificationAttributes = #[#"id"];
[userMapping addAttributeMappingsFromDictionary:#{
#"birthday":#"birthday",
#"email":#"email",
#"facebook_token":#"facebook_token",
#"first_name":#"first_name",
#"gender":#"gender",
#"interested_in":#"interested_in",
#"last_name":#"last_name",
#"password":#"password",
#"m8_status":#"m8_status",
#"status":#"status",
#"languages": #"languages",
#"hometown":#"hometown",
#"location":#"location",
#"avatar_url":#"avatar_url",
#"mood":#"mood"
}];
RKEntityMapping *findUserMapping = [RKEntityMapping mappingForEntityForName:NSStringFromClass([FindUser class]) inManagedObjectStore:managedObjectStore];
findUserMapping.identificationAttributes = #[#"id"];
[findUserMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"users" toKeyPath:#"users" withMapping:userMapping]];
[manager addResponseDescriptorsFromArray:#[
[RKResponseDescriptor responseDescriptorWithMapping:findUserMapping
pathPattern:#"/v1/users"
keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]
]];
This is getting objects from example JSON:
{
"users": [
{
"avatar_url": null,
"birthday": "04/11/1984",
"email": "antonina.duminskaya#wildermancruickshank.co.uk", "first_name": "Antonina",
"gender": "female",
"hometown": null,
"id": "5264ee384d61632c4b2f0000",
"interested_in": [
"male",
"female" ],
"languages": [], "last_name": "Duminskaya", "location": null, "m8_status": null,
"mood": null,
"status": null
} ]
}
here:
[objectManager getObjectsAtPath:#"/v1/users" parameters:#{#"session_id":session.id} success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
FindUser *findUsers = [[self.fetchedFindUsersResultsController fetchedObjects] lastObject];
NSLog(#"%#", findUsers.users);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
}];
I have message in log:
Relationship 'users' fault on managed object (0x8e964a0) <FindUser: 0x8e964a0> (entity: FindUser; id: 0x8da0790 <x-coredata://083BB731-FDD7-4A35-A174-724F50862A02/FindUser/p1> ; data: {
id = nil;
users = "<relationship fault: 0x8deb260 'users'>";
})
And In mappingResults I have all results about json
Core Data is representing the relationship initially as fault saving memory by not loading all the object graph at once.
https://developer.apple.com/library/ios/documentation/cocoa/conceptual/CoreData/Articles/cdFaultingUniquing.html
Also, do not use the reserved word 'id' as the Restkit identification attribute for your managed objects. Try using findUserId and userId and update your mappings accordingly.
This is the JSON return, I'm trying to map the "result"->"id" and add it to the Slides Entity in Core Data, everything so far is mapping I just don't know how to access that parent id and map it into the Slides Entity
{
"result": {
"id": 47,
"type": "Slidedeck",
"name": "Brendan Demo",
"version": "0.24",
"slides": [
{
"id": 767,
"label": "",
"order": 1,
"notes": "",
"file": "slide-38.jpg",
"url": "http://api.testapp.com/v4/resources/767/download/slide",
"thumb": "http://api.testapp.com/v4/resources/767/download/slide?thumb=true",
"components": {
"hotspots": []
}
},
{
"id": 768,
"label": "",
"order": 2,
"notes": "",
"file": "slide-54.png",
"url": "http://api.testapp.com/v4/resources/768/download/slide",
"thumb": "http://api.testapp.com/v4/resources/768/download/slide?thumb=true",
"components": {
"hotspots": []
}
}
]
},
"status": {
"code": 200,
"message": "OK"
}
}
My Entity Mapping and RKResponsedescriptor is as follows:
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:#"Slides"
inManagedObjectStore:[RKManagedObjectStore defaultStore]];
[mapping addAttributeMappingsFromDictionary:#{#"id": #"slideID",
#"label": #"slideLabel",
#"order": #"slideOrder",
#"notes": #"slideNotes",
#"file": #"slideFile",
#"url": #"slideURL",
#"thumb": #"slideThumb"
}];
slidesResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:resourcesEntityMapping method:RKRequestMethodAny pathPattern:[NSString stringWithFormat:#"%#%#", VERSION, #"/resources/:slideDeckID"] keyPath:#"result.slides" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
Any ideas? I've omitted all my previous attempts to accomplish this to keep the code clear. So currently there is no failed attempt code here to grab that parent result id.
EDIT:
I've added the #parent to the RKEntityMapping as mentioned
[mapping addAttributeMappingsFromDictionary:#{#"#parent.id":#"slideDeckID",
#"id": #"slideID",
#"label": #"slideLabel",
#"order": #"slideOrder",
#"notes": #"slideNotes",
#"file": #"slideFile",
#"url": #"slideURL",
#"thumb": #"slideThumb"
}];
also tried:
[mapping addAttributeMappingsFromDictionary:#{#"#parent.slide.id":#"slideDeckID",
#"id": #"slideID",
#"label": #"slideLabel",
#"order": #"slideOrder",
#"notes": #"slideNotes",
#"file": #"slideFile",
#"url": #"slideURL",
#"thumb": #"slideThumb"
}];
but I always get the following trace messages:
Did not find mappable attribute value keyPath '#parent.id'
or
Did not find mappable attribute value keyPath '#parent.result.id'
Any ideas what I'm doing wrong.
EDIT
Checked my logs, when I set the keypath to the following:
reourcesResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:resourcesEntityMapping method:RKRequestMethodGET pathPattern:[NSString stringWithFormat:#"%#%#", VERSION, #"/resources/:slideDeckID"] keyPath:#"result.slides" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
RestKit logs the following:
restkit.object_mapping:RKMapperOperation.m:231 Asked to map source object {
components = {
hotspots = (
);
};
file = "slide-54.png";
id = 768;
label = "";
notes = "";
order = 2;
thumb = "http://api.idetailapp.com/v4/resources/768/download/slide?thumb=true";
url = "http://api.idetailapp.com/v4/resources/768/download/slide";
} with mapping <RKEntityMapping:0x8e30af0 objectClass=Resources propertyMappings=(
"<RKAttributeMapping: 0x8e31b50 thumb => slideThumb>",
"<RKAttributeMapping: 0x8e31a80 #parent.id => slideDeckID>",
"<RKAttributeMapping: 0x8e31aa0 id => slideID>",
"<RKAttributeMapping: 0x8e31ad0 label => slideLabel>",
"<RKAttributeMapping: 0x8e31bb0 order => slideOrder>",
"<RKAttributeMapping: 0x8e31a50 notes => slideNotes>",
"<RKAttributeMapping: 0x8e31c00 file => slideFile>",
"<RKAttributeMapping: 0x8e31cd0 url => slideURL>"
)>
Even though my response json contains the parent id element, it doesn't seem to be there to parse:
{
result = {
id = 47;
name = "Russell Demo";
slides = (
{
components = {
hotspots = (
);
};
file = "slide-38.jpg";
id = 767;
label = "";
notes = "";
order = 1;
thumb = "http://api.idetailapp.com/v4/resources/767/download/slide?thumb=true";
url = "http://api.idetailapp.com/v4/resources/767/download/slide";
},
{
components = {
hotspots = (
);
};
file = "slide-54.png";
id = 768;
label = "";
notes = "";
order = 2;
thumb = "http://api.idetailapp.com/v4/resources/768/download/slide?thumb=true";
url = "http://api.idetailapp.com/v4/resources/768/download/slide";
}
);
type = Slidedeck;
version = "0.24";
};
status = {
code = 200;
message = OK;
};
}
I've checked my RestKit version and get the same results when working with Development and Master branch. Verified that #parent is provided in this release in the RKMappingOperation class. Anything I'm missing here?
Edit:
Added the following relationship to my slides Entity mapping, but I'm still not able to get the container "result" data
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:#"Slides"
inManagedObjectStore:[RKManagedObjectStore defaultStore]];
[mapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"result"
toKeyPath:#"slides"
withMapping:mapping]];
[mapping addAttributeMappingsFromDictionary:#{#"#parent.id":#"slideDeckID",
#"id": #"slideID",
#"label": #"slideLabel",
#"order": #"slideOrder",
#"notes": #"slideNotes",
#"file": #"slideFile",
#"url": #"slideURL",
#"thumb": #"slideThumb"
}];
The latest version of RestKit defines the #parent metadata key which is available during mapping. So you can add a mapping like #parent.id : #"..." to extract the result id while mapping the slide content.
I have the following JSON structure:
{
"category": "MyCategory"
"objects": [
{ "id": 1, "name": "A" },
{ "id": 2, "name": "B" },
{ "id": 3, "name": "C" }
]
}
I'm mapping each object to a separate Core Data entity like this:
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:#"Object" inManagedObjectStore:manager.managedObjectStore];
mapping.identificationAttributes = #[ #"id" ];
[mapping addAttributeMappingsFromDictionary:#{ #"name": #"name" }];
How do I configure the mapping to store their shared category on each object?
The thing I would like is to be able to traverse upwards in the JSON like this:
[mapping addAttributeMappingsFromDictionary:#{ #"name": #"name", #"PARENT.category": #"category" }];
As of now (RestKit 0.20.1), there is no way to do this by using the mapping engine.
FUTURE
There is a new metadata feature under development making it possible to access the parent object:
[mapping addAttributeMappingsFromDictionary:#{ #"name": #"name", #"#metadata.parentObject.category": #"category" }];
PRESENT
I'm modifying my deserialized response using willMapDeserializedResponseBlock. I've added a category on RKObjectManager to make it easy to modify the response:
https://gist.github.com/gunnarblom/5677324
I get a JSON object from a HTML request which contains a hierarchical structure.
Sample from JSON object:
{
"_id": "4f870f064f95ae0da8000002",
"name": "Category",
"parent_id": null
},
{
"_id": "4f870f0e4f95ae0da8000004",
"name": "Brand",
"parent_id": null
},
{
"_id": "4f8715bd4f95ae0da8000028",
"name": "Dermalogica",
"parent_id": "4f870f0e4f95ae0da8000004"
},
{
"_id": "4f8715de4f95ae0da800002a",
"name": "Molton Brown",
"parent_id": "4f870f0e4f95ae0da8000004"
},
{
"_id": "4f8715ea4f95ae0da800002c",
"name": "Waxing",
"parent_id": "4f870f064f95ae0da8000002"
},
{
"_id": "4f8715f34f95ae0da800002e",
"name": "Mens Hair",
"parent_id": "4f870f064f95ae0da8000002"
},
{
"_id": "4f8715fd4f95ae0da8000030",
"name": "Ladies Hair",
"parent_id": "4f870f064f95ae0da8000002"
},
{
"_id": "4f87161f4f95ae0da8000032",
"name": "Massage",
"parent_id": "4f870f064f95ae0da8000002"
}
When I save it in the same way in just one entity, how would I define the fetch request (sorting) so that the objects are sorted with their parent / child relation ?
There is no way to sort this kind of data using sortDescriptors.
This is how I solve the problem, having comments of article with threaded style discussions. After I download all comments, I need to reindexComments
-(void)reindexComments{
NSArray *articleComments = self.comments.allObjects;
[self fetchChildsWithComments:articleComments forParentId:0 num:1];
}
-(NSUInteger)fetchChildsWithComments:(NSArray*)articleComments forParentId:(NSUInteger)parentId num:(NSUInteger)num{
NSArray *childComments = [articleComments filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"parentId == %u", parentId]];
childComments = [childComments sortedArrayUsingComparator:^NSComparisonResult(Comment *c1, Comment *c2) {
if (c1.commentIdValue < c2.commentIdValue){
return NSOrderedAscending;
}else{
return NSOrderedDescending;
}
}];
for (Comment *newRootComment in childComments){
newRootComment.numValue = num;
num++;
num = [self fetchChildsWithComments:articleComments forParentId:newRootComment.commentIdValue num:num];
}
return num;
}
and finally I just sort by numValue field to get my nice threaded discussion
One Way - to use NSOrderedSet - http://developer.apple.com/library/mac/#documentation/Foundation/Reference/NSOrderedSet_Class/Reference/Reference.html
Second,more convinient(since NSOrderedSet is introduced in iOS 5),just a soimple NSFetchRequest with sortDescriptors.Since it is an Array, you can use a many descritors at a time as you want. So, using descriptors by parent_id and id should give you desired result.
NSFetchRequest *request = [[NSFetchRequest alloc]init];
request.entity = [NSEntityDescription entityForName:#"Child" inManagedObjectContext:context];
// request.predicate = [NSPredicate predicateWithFormat:#"parent_id = %#",parent_ID];You don't need any predicate,right?
request.sortDescriptors = [NSArray arrayWithObjects:[NSSortDescriptor sortDescriptorWithKey:#"parent_id" ascending:YES],[NSSortDescriptor sortDescriptorWithKey:#"_id" ascending:YES],nil];
return [context executeFetchRequest:request error:&error];
And,in Objective-C it's not convinient to use underlines in names.
Hope, that helps.