RestKit: how to make a mapping for varying key names - iphone

I'm trying to make a dynamic mapping for a bit weird structured JSON.
I have "array mapped to object" sort of thins so that array indices
ake keys e.g.:
{
"0": {object},
"1": {another object},
"2": {yet another object},
...
}
All objects are of the same type so they can be parsed using the same
mapping, but how to deal with varying key names?

Check out the section on "Handling Dynamic Nesting Attributes" in the Object Mapping docs.
He walks through an example (copied here) with the JSON:
{ "blake": {
"email": "blake#restkit.org",
"favorite_animal": "Monkey"
}
}
Corresponding to the User class:
#interface User : NSObject
#property (nonatomic, retain) NSString* email
#property (nonatomic, retain) NSString* username;
#property (nonatomic, retain) NSString* favoriteAnimal;
#end
Which, you'll notice, the username property corresponds to the key of the JSON.
In order to map it, he uses a special parenthesis syntax to indicate that they key itself is a property:
RKObjectMapping* mapping = [RKObjectMapping mappingForClass:[User class] ];
[mapping mapKeyOfNestedDictionaryToAttribute:#"username"];
[mapping mapFromKeyPath:#"(username).email" toAttribute:"email"];
[mapping mapFromKeyPath:#"(username).favorite_animal" toAttribute:"favoriteAnimal"];
Hope this helps!

Related

How to filter an array using an array?

I have three types of custom classes (see below) and three arrays componentList, componentGroupList and componentGroupItemList. Arrays are not linked, each of them contain all objects. I need to filter specific component, all its related groups and all their related items.
Now, I know how to filter componentList using #"componentId==123" and get desired component object. I can also filter its groups from componentGroupList using the same predicate, because ComponentGroup object contains the same componentId key. However, I don't know how to filter related ComponentGroupItem objects from componentGroupItemList.
Currently, I have filtered array containing ComponentGroup objects, and I would like to filter componentGroupItemList using that array. Is it possible, or do I need to extract all "groupId" values from filteredComponentGroupList into a string and then make some predicate?
The classes:
#interface Component : NSObject
#property (nonatomic, strong) NSNumber *componentId;
#property (nonatomic, strong) NSString *title;
#end
#interface ComponentGroup : NSObject
#property (nonatomic, strong) NSNumber *groupId;
#property (nonatomic, strong) NSNumber *componentId;
#property (nonatomic, strong) NSString *title;
#end
#interface ComponentGroupItem : NSObject
#property (nonatomic, strong) NSNumber *itemId;
#property (nonatomic, strong) NSNumber *groupId;
#property (nonatomic, strong) NSString *title;
#end
You have to extract the group ids first
NSArray *groupIds = [filteredComponentGroupList valueForKey:#"groupId"];
and use that for a predicate
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"groupId IN %#", groupIds];
NSArray *filteredComponentGroupItemList = [componentGroupItemList filteredArrayUsingPredicate:predicate];
At a first glance your data structure seems a bit redundant, but I guess you have thought it through.
If I understand your requirements correctly, you have an array of component groups already filtered (let's call it filteredComponentGroups) and you wish to filter another array (componentGroupItemList) using filteredComponentGroups.
In that case you can use the IN operator of NSPredicate and construct the array of IDs with valueForKey: on the array. valueForKey: on an array constructs a new array with only the values of that key of each object in the original collection. Very powerful for situations like this one.
NSArray *filteredComponentGroups = // ... your filtered components
NSArray *componentIdsFromFilteredComponentGroups = [filteredComponentGroups valueForKey: #"groupId"];
NSPredicate *inFilteredComponentGroupsP = [NSPredicate predicateWithFormat: #"groupId IN %#", componentIdsFromFilteredComponentGroups];
NSArray *filteredGroupItemList = [componentGroupItemList filteredArrayUsingPredicate: inFilteredComponentGroupsP];
Typed directly in the browser, so beware of typos.

JSON Dictionary to Core Data

I have a JSON response that I turn into a Dictionary like so:
NSError *error;
self.restKitResponseDict = [NSDictionary dictionaryWithDictionary:[NSJSONSerialization JSONObjectWithData:response.body options:0 error:&error]];
I have a core data class that has the following attributes/properties:
name
image_url
When I log the restKitResponseDict from above I see that image_url is listed as "image_url" like this:
name = Rock;
"image_url" = "http://f.cl.ly/items/122s3f1M1E1p432B211Q/catstronaut.jpg";
Is this why KVC is crashing on
[CoreDataClass setValuesForKeysWithDictionary:self.restKitResponseDict];
like this:
'[<CoreDataClass 0x14132c> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key image_url.'
Do the quotes matter? Should I ask my server guy to get rid of the underscore that's likely causing it?
Core Data Class:
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#interface CoreDataClass : NSManagedObject
#property (nonatomic, retain) NSString * name;
#property (nonatomic, retain) NSString * image_url;
#end
#implementation CoreDataClass
#dynamic name;
#dynamic image_url;
#end
You are sending the method to the class object:
[CoreDataClass setValuesForKeysWithDictionary:self.restKitResponseDict];
when you probably want to send it to the actual CoreDataClass instance:
[coreDataClassObject setValuesForKeysWithDictionary:self.restKitResponseDict];
EDIT
what's the simplest way to init the object from the class? – Eric
It's a subclass of NSManagedObject, so you use the normal Core Data methods. One way to create a new object:
CoreDataClass *coreDataObject = [NSEntityDescription
insertNewObjectForEntityForName:#"YOUR_ENTITY_NAME"
inManagedObjectContext:managedObjectContext];
If you need basic information about using Core Data, see the Core Data Programming Guide: http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/coredata/cdProgrammingGuide.html#//apple_ref/doc/uid/TP30001200-SW1
The NSLog() with the %# format uses the description method to print objects, and the description of a NSDictionary puts quotation marks around all keys and values that contain any special characters, such as the underscore. For example
NSDictionary *dict = #{
#"key1" : #"value_1",
#"key_2" : #"value2"
};
NSLog(#"dict=%#", dict);
produces
2012-08-25 18:15:33.553 test27[3416:c07] dict={
key1 = "value_1";
"key_2" = value2;
}
Therefore the key in your JSON dictionary does not really have quotation marks, and the underscore is probably not the cause of the error.
The error message indicates that the managed object does not have an image_url attribute, therefore you should check that.

RestKit: mapping of dynamic attribute (not a NSDictionary as in the example)

I'm using RestKit in my iOS project, and figured non-trivial problem and can't find solution for it.
I have a json:
[{
"name": "restkit",
"downloads": 2
},
{
"name": "restkit",
"rating": 10.0
}]
and data model: Model.h
#interface Model : NSObject
#property (nonatomic, strong) NSString * name;
#property (nonatomic, strong) NSString * key;
#property (nonatomic, strong) NSString * value;
#end
Those JSON objects loaded in one array, and one of the attributes is a "dynamic" attribute.
After mapping performed in a RestKit I want to be able to have 2 records:
name: "restkit", key: "downloads", value: 2
name: "restkit", key: "rating", value: 10.0
Question: how to map JSON in the beginning into 2 NSObjects as shown in the example above?
This is how I initialize mapping using RestKit and firing requests:
// during app initialization I setup mappings:
RKObjectMapping *modelMapping = [RKObjectMapping mappingForClass:[Model class]];
[metricDataMapping mapKeyPath:#"name" toAttribute:#"name"];
...... -- something should go here to support that dynamic stuff
[[RKObjectManager sharedManager].mappingProvider addObjectMapping:metricDataMapping];
// in the view controller when loading data
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:url usingBlock:^(RKObjectLoader *loader){
loader.objectMapping = [[RKObjectManager sharedManager].mappingProvider objectMappingForClass: [MetricData class]];
loader.onDidLoadObjects = ^(NSArray * objects){
self.dataArray = objects;
};
}];
The problem, is that keys "downloads" and "rating" are dynamic, and it could be any word. I need to parse that 'on-the-fly' and show in UI.
Restkit does support such behavior, but only for nested dictionaries.
You can access the dynamically named properties using key-value coding. After you had made an NSDictionary out of the JSON, do this:
[self setValue:[dictionary objectForKey:#"downloads"] forKey:#"downloads"];
etc.
I did use key-value validation on the model triggered by RestKit to set correct metric key and value to the generic key I want.

Serializing a nested object when posting from iOS to Rails app

Hoping to get a little push in the right direction. I am having trouble getting a nested object to serialize properly when I POST to my rails app using RestKit. I have the following mappings:
RKObjectMapping *cartSerializationMapping = [RKObjectMapping mappingForClass:[TOCart class]];
[cartSerializationMapping mapKeyPath:#"place.placeID" toAttribute:#"order[external_id]"];
//map the line items serialization mapping
RKObjectMapping *lineItemSerializationMapping = [RKObjectMapping mappingForClass:[TOLineItem class]];
[lineItemSerializationMapping mapKeyPath:#"itemID" toAttribute:#"itemID"];
[lineItemSerializationMapping mapKeyPath:#"name" toAttribute:#"name"];
[[RKObjectManager sharedManager].mappingProvider setSerializationMapping:lineItemSerializationMapping forClass:[TOLineItem class]];
//add relationship bw line items to TOLineItem
[cartSerializationMapping mapKeyPath:#"line_items" toRelationship:#"order[line_items]" withMapping:lineItemSerializationMapping serialize:YES];
[[RKObjectManager sharedManager].mappingProvider setSerializationMapping:cartSerializationMapping forClass:[TOCart class]];
After posting to the server, serialization works for the parent object but not for the nested line_item object:
Started POST "/orders" for 127.0.0.1 at 2011-11-16 04:05:58 -0800
Processing by OrdersController#create as JSON
Parameters: {"order"=>{"line_items"=>["<TOLineItem: 0x8aafdb0>"], "external_id"=>"4ae8a535f964a52024b121e3"}}
I want the line_item to serialize to itemID and name etc...
Did I set my mappings incorrectly?
Thanks!
UPDATE:
My TOCart class:
#import <Foundation/Foundation.h>
#class TOPlace;
#interface TOCart : NSObject
{
NSNumber *cartID;
TOPlace *place; //post to external id
NSString *state;
NSMutableArray *line_items;
}
#property (nonatomic, retain) NSNumber *cartID;
#property (nonatomic, retain) TOPlace *place;
#property (nonatomic, retain) NSString *state;
#property (nonatomic, retain) NSMutableArray *line_items;
#end
I always define my mapping to map from API to entities & then create the serialization mapping with [myMappingFromApi inverseMapping] selector. You can find further details in my answer to somewhat different question, but definitely related: RestKit: How does one post an array of objects?.

Restkit OM2 relationship mapping

Im using Restkit OM2 to take in a json and map to objects on iphone.
Im currently confused on how to structure the mappings and could do with some help.
Below is an example json file
{
-magic_verbs: [
-{
lemma: "work"
position: 5
score: "0.75"
value: "working"
}
-{
lemma: "head"
position: 0
score: "0.75"
value: "heading"
}
],
magic_advs: [
-{
lemma: "not"
position: 2
score: "0.6"
value: "not"
}
-{
lemma: "just"
position: 2
score: "0.6"
value: "just"
}
]
}
i only need the lemma and value fields from each of these. so for example the verb class contains
#interface Verbs : NSManagedObject {
}
#property (nonatomic,retain) NSString *lemma;
#property (nonatomic,retain) NSString *value;
#end
#implementation Verbs
#synthesize lemma,value;
#end
then i read in the json and create the mappings with below code
objectManager = [RKObjectManager objectManagerWithBaseURL:#"http://localhost:3000"];
objectManager.objectStore = [RKManagedObjectStore objectStoreWithStoreFilename:#"RKRelationshipMappingExample.sqlite"];
[RKObjectManager setSharedManager:objectManager];
RKObjectMappingProvider* provider = [[RKObjectMappingProvider new] autorelease];
RKObjectMapping* verbMapping = [RKObjectMapping mappingForClass:[Verbs class]];
[verbMapping mapKeyPath:#"lemma" toAttribute:#"lemma"];
[verbMapping mapKeyPath:#"value" toAttribute:#"value"];
[provider setMapping:verbMapping forKeyPath:#"magic_verbs"];
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:#"/api/users/1/magic_words" objectMapping:verbMapping delegate:self];
I have done the same for the adjs mapping. I also have a class called words that contains 2 nsarrays that is to contain the object mapping data. but im unsure how to implement this and link them up correctly.
#interface Words : NSObject {
NSArray *_verbs;
NSArray *_adjs;
}
#property (nonatomic, retain) NSArray *verbs,*adjs;
#end
any help and guidance on this is appreciated. I have looked at the example in catalog project and have been able to get that running but havent been able to master the concept to apply it to my own json files.
thanks
G
I suggest you to look at the new documentation of OM 2.0 that is in the Github page here
if you haven't noticed yet. It clearly lays out on how to map your JSON to an object.