RestKit Shredding JSON Object nodes into Core Data - objective-c

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"];

Related

RestKit parsing nested dictionary

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?

RestKit 0.20.2 generating incorrect JSON output for sending child object collection in post/put operations

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.

RestKit – map single source to multiple destinations with relationship mapping

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"];

NSFetchRequest query returns nil after setting one to many relationship

I have two NSManagedObject subclasses, Book and Category. The two are related by one-to-many relationship.
On Book side, it has relationship categories connected to Category. Inversely Category connected to Book via books.
My app setup is to download the data from my server every time the app opened. The downloaded data will be traversed and save inside the app. In my app, I use Magical Record to interact with CoreData.
Category data will first be downloaded and later saved from JSON format:
{
"categories" : [
{
"id" : 1,
"name" : "Fiction"
},
{
"id" : 2,
"name" : "Non-fiction"
}
]
}
There has been no problem on saving Category. While later on the app lifecycle Book data will be saved from JSON format as following:
{
"books" : [
{
"id" : 1,
"name" : "Fiction 1",
"category_id" : 1
},
{
"id" : 2,
"name" : "Fiction 2",
"category_id" : 2
}
]
}
The problem arises when I want to assign Book relation to Category. My code to assign the relationship:
for (NSDictionary *bookInfo in jsonObject[#"books"]) {
Book *book = [Book createEntity];
book.identifier = bookInfo[#"id"];
book.name = bookInfo[#"name"];
Category *category = [Category findFirstByAttribute:#"identifier" withValue:bookInfo[#"category_id"]];
book.category = category;
[[NSManagedObjectContext contextForCurrentThread] saveNestedContexts];
}
On the first loop, the bookObject was saved successfully. But on the second loop and next, the Category cannot be queried anymore. It returns nil, despite the identifier is the same.
I did perform check on all the Category via [Category findAll] and traversed all the records and found that the Category with the same id still exist on the second and next loop.
The question is why is the findFirstByAttribute:withValue: failed to retrieve Category on the second loop?
I fixed this problem by updating MagicalRecord to the latest version (2.1).
And then I modified the way my NSManagedObject saving method from saveNestedContexts to newly added saveUsingCurrentThreadContextWithBlockAndWait.

Parsing complex JSON using RestKit in Objective C

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.