Given the following Core Data relationships:
Recipe <<---> Cookbook
Recipe <<---> Author
Cookbook <<---> Author
And this source JSON representing a recipe:
{
"id": 123,
"name": "Recipe Name",
"author": {
"id": 456,
"name": "Author Name"
},
"cookbook": {
"id": 789,
"title": "Cookbook Title"
}
}
I'm looking for a RestKit mapping that will result in a recipe where the following are true:
recipe.cookbook IN recipe.author.cookbooks
recipe.cookbook.author === recipe.author
Here's what I have so far:
recipeMapping =
[RKEntityMapping mappingForEntityForName:#"Recipe" inManagedObjectStore:_store];
[recipeMapping addAttributeMappingsFromDictionary:#{
#"id": #"id",
#"name": #"name"
}];
recipeMapping.identificationAttributes = #[#"id"];
RKEntityMapping *authorMapping =
[RKEntityMapping mappingForEntityForName:#"Author" inManagedObjectStore:_store];
[authorMapping addAttributeMappingsFromDictionary:#{
#"id": #"id",
#"name": #"name"
}];
authorMapping.identificationAttributes = #[#"id"];
RKEntityMapping *cookbookMapping =
[RKEntityMapping mappingForEntityForName:#"Cookbook" inManagedObjectStore:_store];
[cookbookMapping addAttributeMappingsFromDictionary:#{
#"id": #"id",
#"title": #"title"
}];
cookbookMapping.identificationAttributes = #[#"id"];
[recipeMapping
addRelationshipMappingWithSourceKeyPath:#"cookbook"
mapping:cookbookMapping];
[recipeMapping
addRelationshipMappingWithSourceKeyPath:#"author"
mapping:authorMapping];
// Now what?
Upon applying this mapping, recipe.cookbook is a proper instance of Cookbook, and recipe.author is a proper instance of Author, but of course recipe.cookbook.author is nil and recipe.author.cookbooks is empty.
You can't really do this while mapping as you don't have the required information.
You might be able to setup a relationship which uses a foreign key, and rather than have a special attribute for the foreign key use the relationship between the 3 objects instead. I'd say this would be a bit fragile though as it would rely on the order in which the relationships were processed during mapping.
Instead, I would look at doing this either during post-processing (i.e. in the callback after the mapping is complete or by implementing willSave on the managed object subclasses) or on-demand when you actually need to know the relationships. This can be done using KVC, something like:
If you execute a fetch to get all authors, you can build the cookbooks for each using:
author.cookbooks = [author.recipes valueForKeyPath:#"#distinctUnionOfObjects.cookbooks"];
Related
Is there a way to construct a mantle object using two different JSON objects? For example, say there is an Address class and the following two JSON formats:
{
"street: "s",
"city" : "city",
"state" : "state",
"zipcode" "12345"
}
{
"street_one: "s1",
"street_two: "s2",
"apartment" : "apt 1",
"city" : "city",
"state" : "state",
"zip" "12345"
}
[MTLJSONAdapter modelOfClass:[Address class] fromJSONDictionary:JSONDictionary error:&error];
Is there somewhere in MTLJSONAdapter to recognize two formats and assign or serialize properties based on them?
+ (NSDictionary *)JSONKeyPathsByPropertyKey
{
if (isJsonV2) {
// do new mapping
}
else {
// do original mapping
}
}
Hope to do something like above, or anything that allows conditionally mapping to the object.
Mantle doesn't support this, but you could use a subclass for V2 which has the extra street entry, or use a protocol to encapsulate the shared behaviour.
I get this json response calling a REST api:
{
"ProfileResponse": {
"Request_timestamp": 1378473780145780000,
"Execution_time": 30135057
},
"Results": {
"newsession": {
"Sessionid": "110873298014",
"Status": "ACTIVE",
"Tag": "GO",
"TimeStamp": 1378473780146026000
}
},
"ResultsCount": 0,
"ResultsURL": ""
}
and I want to parse this response and get the newsession into a session object that have defined as an objective-c class.
I have defined a base_mapping that maps the base fields ('PrifileResponse', 'Results', 'ResultsCount', 'ResultsURL'), and another mapping for the 'newsession' and a relationship mapping:
RKObjectMapping *baseResponseMapping = [RKObjectMapping mappingForClass:[CSBaseResponse class]];
RKObjectMapping *sessionMapping = [RKObjectMapping mappingForClass:[CSSessionResponse class]];
[sessionMapping addAttributeMappingsFromDictionary:#{
#"Sessionid": #"sessionid",
#"Status": #"status"
}
];
RKRelationshipMapping *relationshipMapping = [RKRelationshipMapping relationshipMappingFromKeyPath:#"Results" toKeyPath:#"results" withMapping:sessionMapping];
[baseResponseMapping addPropertyMapping:relationshipMapping];
but I can't access the "newsession",
my question specifically is, is there a syntax that I can use in the "from key path" like Results.newsession or something like that to access the inside of "newsession",
or what is the best practice to parse/map this nested structure?
I have 4 NSManagedObjects i.e. Item, Listing, PaymentMethod and HandlingTime such that Item has 1-1 relation with Listing which has 1-n relation with PaymentMethod and Listing has 1-1 relation with HandlingTIme. When I try to post/put an item, following mapping works for Item-Listing and Listing-HandlingTime relationship for making item post/put server operations but does not generate correct JSON for Listing-PaymentMethods.
NSDictionary *itemRKRequestMapping = #{
#"id" : #"eid",
#"title" : #"title",
#"listing.id" : #"listing.eid",
#"listing.title" : #"listing.title",
#"listing.item.id" : #"listing.item.eid",
#"listing.handlingTime.id" : #"listing.handlingTime.eid",
#"listing.handlingTime.title" : #"listing.handlingTime.title",
#"listing.paymentMethods.id" : #"listing.paymentMethods.eid",
#"listing.paymentMethods.title" : #"listing.paymentMethods.title"
};
It produces the following JSON for RKRequest operation when calling
[[RKObjectManager sharedManager] putObject:self.item path:#"items/1" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"Success"); } failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Failure saving item: %#", error.localizedDescription);
}];
In correct JSON for paymentMethods.
{"id":1, "title":"item1", "listing":{"id":1,"item":{"id":1},"handlingTime":{"id":1, "title":"20 days"},"paymentMethods":{"id" : [1,2],"title":["VISA","MASTER"]} } }
Following is the desired correct JSON output for paymentMethods.
{"id":1, "title":"item1", "listing":{"id":1,"item":{"id":1},"handlingTime":{"id":1, "title":"20 days"},"paymentMethods": [ { "id":1, "title": "VISA"}, { "id":2, "title": "MASTER"} ] } }
GET operation works fine for this object model as thats setup using the relationshipMappingFromKeyPath for item- listing relation as shown below.
[itemMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"listing" toKeyPath:#"listing" withMapping:listingMapping]];
[listingMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"handlingTime" toKeyPath:#"handlingTime" withMapping:handlingTimeMapping]];
[listingMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"paymentMethods" toKeyPath:#"paymentMethods" withMapping:paymentMethodMapping]];
Can any one please point out the mistake in the mappings for RKRequest operations above?
Your problem is that the relationship contains only a single item so RestKit doesn't know that it should be mapped into an array. To teach RestKit, you need to separate the mapping for the appropriate key, explicitly create an attribute mapping for it, set the mapping to forceCollectionMapping and then add it to your itemRKRequestMapping.
forceCollectionMapping docs are here.
I have a JSON structure that needs to be shredded into several core data tables. However with the JSON I am getting back isn't really normalized to a structure I can figure out how to split them into objects. I have 4 tables, Songs, Artist, Album, Genre.
JSON:
{
"Songs": [
{
"strFileID": "b7eccc27-b099-4ea2-93c5-5004f6d2e31d",
"strFileName": "07-Cryin.mp3",
"strDuration": "00:05:09.812",
"strAlbumTitle": "Big Ones",
"strArtists": "Aerosmith",
"strTrackNumber": 7,
"strGenre": "Rock",
"strTitle": "07-Cryin.mp3"
}
]
}
Since strArtists isn't split into it's own object and is just an element, I can't figure out how to create the mapping to ensure only "Aerosmith" is ripped out into it's own table and then related back.
Currently I have the following as a starting point to just get song and the artist:
RKManagedObjectMapping *artistMapping = [RKManagedObjectMapping mappingForClass:[Artist class]];
artistMapping.primaryKeyAttribute = ArtistAttributes.artistName;
[artistMapping mapKeyPath:#"Song.strArtists" toAttribute:ArtistAttributes.artistName];
RKManagedObjectMapping *songMapping = [RKManagedObjectMapping mappingForClass:[Song class]];
songMapping.primaryKeyAttribute = SongAttributes.fileId;
[songMapping mapKeyPath:#"strFileID" toAttribute:SongAttributes.fileId];
[songMapping mapKeyPath:#"strFileName" toAttribute:SongAttributes.fileName];
[songMapping mapKeyPath:#"strTitle" toAttribute:SongAttributes.title];
[songMapping mapKeyPath:#"strDuration" toAttribute:SongAttributes.duration];
[songMapping mapKeyPath:#"strTrackNumber" toAttribute:SongAttributes.trackNumber];
[songMapping mapRelationship:#"artist" withMapping:artistMapping];
[objectManager.mappingProvider setMapping:songMapping forKeyPath:#"Song"];
[objectManager.mappingProvider setMapping:artistMapping forKeyPath:#"strArtists"];
I've been having issues with this issue for several days now, and would really appreciate it if someone will be able to provide some guidance or even a full code solution for this.
I'm trying to perform dynamic object mapping to a JSON string in Objective C using RestKit and can't seem to get the right values.
This is an example to the kind of JSON response I need to parse:
{
"Boy" :
{
"favoriteClass" : "math",
"basicInfo" :
{
"name" : "John",
"age" : 10,
"type" : 1
}
"friends" :
[
{
"Boy" :
{
"favoriteClass" : "PE"
"basicInfo" :
{
"name" : "Bill",
"age" : 12,
"type" : 1
}
"friends" : []
},
"Girl" :
{
"favoriteTeacher" : "Mrs. Manson"
"basicInfo" :
{
"name" : "Sara",
"age" : 11,
"type" : 2
}
"friends" : []
},
"Girl" :
{
"favoriteTeacher" : "Mr. Chase"
"basicInfo" :
{
"name" : "Ronda",
"age" : 9,
"type" : 2
}
"friends" : []
}
}
]
}
}
Meaning, I have two types of classes: a Buy class and a Girl class.
They each have different fields (favoriteClass for the boys and favoriteTeacher for the girls), but the both have a basicInfo field, which contains exactly the same structure.
I can tell which should be mapped to the Boy class and which should be mapped to the Girl class with using the name of the record in the array ("Boy" or "Girl"), or by the "type" field's value within the "basicInfo" record (1 for boys and 2 for girls).
The friends array for both a Boy and a Girl class can contain instances of both boys and girls.
Can anybody please give me some pointers on how this can be done?
Any help will be greatly appreciated.
Thanks,
I just started looking at RestKit today, so I am far from an authority. However, looking at Blake's excellent documentation, it seems to me that you are overly complicating this (your posted JSON does not validate BTW). Unless you have a specific need to have a BasicInfo object, I'd remove it and configure your JSON thus:
{
"people": [
{
"age": 10,
"favoriteClass": "math",
"name": "John",
"type": 1,
"friends": [
{
"age": 12,
"favoriteClass": "PE",
"friends": [],
"name": "Bill",
"type": 1
},
{
"age": 11,
"favoriteTeacher": "Mrs. Manson",
"friends": [],
"name": "Sara",
"type": 2
},
{
"age": 9,
"favoriteTeacher": "Mr. Chase",
"friends": [],
"name": "Ronda",
"type": 2
}
]
}
]
}
Then you should be able to utilize approach (1) that he describes in the Dynamic Object Mapping section of the Object Mapping document. It appears to me that the only difference you have in comparison with the example he gives is that you have some additional ivars in each of the Boy and Girl classes, and you are using an number rather than a string to identify Boy vs Girl. You can deal with that by modifying the declarations he gives to be:
// Basic setup
RKObjectMapping* boyMapping = [RKObjectMapping mappingForClass:[Boy class]];
[boyMapping mapAttributes:#"age", #"name", #"favoriteClass",nil];
RKObjectMapping* girlMapping = [RKObjectMapping mappingForClass:[Girl class]];
[girlMapping mapAttributes:#"age", #"name", #"favoriteTeacher", nil];
// Configure the dynamic mapping via matchers
[dynamicMapping setObjectMapping:boyMapping whenValueOfKeyPath:#"type" isEqualTo:#"1"];
[dynamicMapping setObjectMapping:girlMapping whenValueOfKeyPath:#"type" isEqualTo:#"2"];
The type of the matcher may not be quite right -- as I said, I just started reading this stuff today.
Just go through the hierarchy in a disciplined way.
//pseudo code
for (id child in JSONObject) {
if (it is a boy) create new boy object;
else create new girl object;
fill in the info;
for (id child in friends) {
if (it is a boy) create new boy object;
else create new girl object;
fill in the info;
}
}
Of course the whole data scheme is ridiculously repetitive. In my opinion, you should have only one class Child that anyway has an attribute basicInfo.type that identifies it as boy or girl, so no need to have an extra class.
Also, if you have a list of children who are friends with each other, you will end up listing many children multiple times. Much better would be to have a simple array with unique IDs in the friends field.
You simply need to use a JSON parser like SBJSON, SBJSON can parse the data you receive like so:
SBJsonParser *parser = [[SBJsonParser alloc] init];
NSDictionary *received = [parser objectWithData:data];
However, if you want to use Restkit, it has a built in parser also. I believe it would work something like this:
- (void)request:(RKRequest*)request didLoadResponse:(RKResponse*)response
{
RKJSONParser* parser = [RKJSONParser new];
NSDictionary *dict = [parser objectFromString:[response bodyAsString]];
}
I hope that helps you.