RestKit - Entity Mapping Property transformation - objective-c

I need to map a web service with object properties all in caps against coredata object properties all in lowercase:
# JSON
{
'ID': 'value',
'TITLE': 'value',
'BODY': 'value',
}
# CoreData Entity
{
'id': 'value',
'title': 'value',
'body': 'value',
}
I mapped the fields by doing:
RKEntityMapping *entMap = [RKEntityMapping mappingForEntityForName:entName
inManagedObjectStore:managedObjectStore];
NSEntityDescription *entity = [[managedObjectModel entitiesByName]
objectForKey:entName];
[entMap addAttributeMappingsFromArray:[[[RKPropertyInspector sharedInspector]
propertyInspectionForEntity:entity] allKeys]];
I saw this convenient setDefaultSourceToDestinationKeyTransformationBlock fonction in RKObjectMapping allowing to define custom transformation on object properties. This is not available on RKEntityMapping.
How can I do property transformations with RKEntityMapping without defining fields manually ?

Here is my current work-around:
- (NSDictionary *)dictToArray:(NSArray *)array
withTranformation:(NSString *(^)(NSString *))theBlock
{
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
for (NSString *key in array) {
dict[theBlock(key)] = key;
}
return dict;
}
NSArray *fields = [[[RKPropertyInspector sharedInspector]
propertyInspectionForEntity:entity] allKeys];
NSDictionary *mapDict = [self dictToArray:fields
withTranformation:^NSString *(NSString *str) {
return [str uppercaseString];
}];
[entMap addAttributeMappingsFromDictionary:mapDict];

Related

Group same named NSURLComponents queryItems into NSDictionary as array

With no objective-C knowledge I am currently stuck at, what would be a simple task in other languages I know.
For a query string like this:
name1=value1&name2=value2&name1=value3
I need to end up with a NSDictionary in this shape:
#{
#"name1": #{
someField: #[
#"value1",
#"value3",
]
anotherField: #YES,
},
#"name2": #{
someField: #[
#"value2",
]
anotherField: #YES,
}
}
In javascript I could solve this by:
queryItems.reduce((result, item) => {
resultItem = result[item.name] || {
someField: [],
anotherField: true,
}
resultItem.someField.push(item.value)
return {
...result,
[item.name]: resultItem,
}
}, {})
I found this How do I convert url.query to a dictionary in Swift? but I am stuck with Objective-C in this project.
Check the function bellow. I hope I understand what you mean.
NSDictionary* queryItemsToDictionary(NSString* items) {
NSURLComponents* components = [[NSURLComponents alloc] initWithString:[#"?" stringByAppendingString:items]];
NSMutableDictionary* result = [NSMutableDictionary new];
for (NSURLQueryItem* item in components.queryItems) {
NSDictionary* valueForItem = [result objectForKey:item.name];
NSArray* someFieldValues = [valueForItem objectForKey:#"someField"];
if (someFieldValues == nil) {
someFieldValues = #[];
}
[result setObject:#{
#"someField": [someFieldValues arrayByAddingObject:item.value],
#"anotherField": #YES
} forKey:item.name];
}
return result;
}
This is how you can try it:
NSDictionary* dictionary = queryItemsToDictionary(#"name1=value1&name2=value2&name1=value3");
NSLog(#"%#", dictionary);

Add objects to NSMutable array with grouping

I want my NSArray sampleData to receive actual data from parse.com database assuming like this:
self.sampleData = #[ #{ #"date": #"12/5/2014",
#"group": #[ #{ #"text": #"post1", #"location": #"x,y" },
#{ #"text": #"post2", #"location": #"x,y" },
#{ #"text": #"post3", #"location": #"x,y" },
#{ #"text": #"post4", #"location": #"x,y" },
#{ #"text": #"post5", #"location": #"x,y" }
]
},
#{ #"date": #"12/3/2014",
#"group": #[ #{ #"text": #"post6", #"location": #"x,y" },
#{ #"text": #"post7", #"location": #"x,y" },
#{ #"text": #"post8", #"location": #"x,y" },
#{ #"text": #"post9", #"location": #"x,y" },
#{ #"text": #"post10", #"location": #"x,y" }
]
}
];
As you can see, I want to group text and location by date, so that I can display them in a view with date as header and text/location as content.
Here below is what I'm capable doing so far:
PFQuery *postQuery = [PFQuery queryWithClassName:kPAWParsePostsClassKey];
[postQuery whereKey:kPAWParseUserKey equalTo:[PFUser currentUser]];
postQuery.cachePolicy = kPFCachePolicyNetworkElseCache;
postQuery.limit = 20;
[postQuery findObjectsInBackgroundWithBlock:^(NSArray *myPosts, NSError *error)
{
if( !error )
{
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"MM/dd/yyyy"];
NSMutableArray *objectArray = [NSMutableArray new];
for (PFObject *object in myPosts) {
[objectArray addObject:#{#"createdAt": [formatter stringFromDate:object.createdAt], #"text": [object objectForKey:#"text"], #"location": [object objectForKey:#"location"]}];
}
self.sampleData = objectArray;
NSLog(#"My sampleData --> %#", self.sampleData);
}
}
];
The above code is obvious there's no grouping whatsoever, so really need help here.
Okay, so you have an array of items, and you want to group them into sections based on a particular key.
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"MM/dd/yyyy"];
// Sparse dictionary, containing keys for "days with posts"
NSMutableDictionary *daysWithPosts = [NSMutableDictionary dictionary];
[myPosts enumerateObjectsUsingBlock:^(PFObject *object, NSUInteger idx, BOOL *stop) {
NSString *dateString = [formatter stringFromDate:[object createdAt]];
// Check to see if we have a day already.
NSMutableArray *posts = [daysWithPosts objectForKey: dateString];
// If not, create it
if (posts == nil || (id)posts == [NSNull null])
{
posts = [NSMutableArray arrayWithCapacity:1];
[daysWithPosts setObject:posts forKey: dateString];
}
// add post to day
[posts addObject:obj];
}];
// Sort Dictionary Keys by Date
NSArray *unsortedSectionTitles = [daysWithPosts allKeys];
NSArray *sortedSectionTitles = [unsortedSectionTitles sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
NSDate *date1 = [formatter dateFromString:obj1];
NSDate *date2 = [formatter dateFromString:obj2];
return [date2 compare:date1];
}];
NSMutableArray *sortedData = [NSMutableArray arrayWithCapacity:sortedSectionTitles.count];
// Put Data into correct format:
[sortedSectionTitles enumerateObjectsUsingBlock:^(NSString *dateString, NSUInteger idx, BOOL *stop) {
NSArray *group = daysWithPosts[dateString];
NSDictionary *dictionary = #{ #"date":dateString,
#"group":group };
[sortedData addObject:dictionary];
}];
self.sampleData = sortedData;
This code will not generate exactly what you asked for. It will generate something that looks like this:
sampleData = #[ #{ #"date": #"12/5/2014",
#"group": ##[ PFObject*,
PFObject*,
PFObject*,
PFObject*,
PFObject*,
]
},
#{ #"date": #"12/3/2014",
#"group": #[ PFObject*,
PFObject*,
PFObject*,
PFObject*,
PFObject
]
}
];
There's no need to convert your PFObject* in the myPosts array into #{ #"text": #"post5", #"location": #"x,y" } since you'll lose access to other pieces of information. Here is how you would use this sampleData array.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView; {
return self.sampleData.count;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section; {
return self.sampleData[section][#"date"];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section; {
return self.sampleData[section][#"group"].count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath; {
PFObject *post = self.sampleData[indexPath.section][#"group"][indexPath.row];
UITableViewCell *cell = // dequeue A reusable tableviewcell here
// configure the cell here
return cell;
}

RestKit nested relationships

Working with RestKit, I'm trying to map the following JSON.
{
"userID": 1,
"collections": [
{
"collectionID": 120,
"friends": [
{
"friendID": 6,
"orders": [
{
"orderID": 1,
"name": "Small"
}
]
}
]
},
{
"collectionID": 123,
"friends": [
{
"friendID": 6,
"orders": [
{
"orderID": 2,
"name": "Medium"
}
]
}
]
}
]
}
I'm utilising RestKit and MagicalRecord - setup code, mapping and relationships below
NSURL *modelURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"Model" ofType:#"momd"]];
NSManagedObjectModel *managedObjectModel = [[[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL] mutableCopy];
RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
NSString *storePath = [RKApplicationDataDirectory() stringByAppendingPathComponent:#"db.sqlite"];
[managedObjectStore addSQLitePersistentStoreAtPath:storePath fromSeedDatabaseAtPath:nil withConfiguration:nil options:nil error:nil];
[managedObjectStore createManagedObjectContexts];
// Configure MagicalRecord to use RestKit's Core Data stack
[NSPersistentStoreCoordinator MR_setDefaultStoreCoordinator:managedObjectStore.persistentStoreCoordinator];
[NSManagedObjectContext MR_setRootSavingContext:managedObjectStore.persistentStoreManagedObjectContext];
[NSManagedObjectContext MR_setDefaultContext:managedObjectStore.mainQueueManagedObjectContext];
RKObjectManager *objectManager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:#"http://127.0.0.1:3500"]];
objectManager.managedObjectStore = managedObjectStore;
RKEntityMapping *appMapping = [RKEntityMapping mappingForEntityForName:#"App" inManagedObjectStore:managedObjectStore];
appMapping.identificationAttributes = #[ #"userID" ];
RKEntityMapping *collectionMapping = [RKEntityMapping mappingForEntityForName:#"Collection" inManagedObjectStore:managedObjectStore];
collectionMapping.identificationAttributes = #[ #"collectionID" ];
[collectionMapping addAttributeMappingsFromArray:#[#"collectionID"]];
RKEntityMapping *friendMapping = [RKEntityMapping mappingForEntityForName:#"Friend" inManagedObjectStore:managedObjectStore];
friendMapping.identificationAttributes = #[ #"friendID" ];
[friendMapping addAttributeMappingsFromArray:#[#"friendID"]];
RKEntityMapping *orderMapping = [RKEntityMapping mappingForEntityForName:#"Order" inManagedObjectStore:managedObjectStore];
orderMapping.identificationAttributes = #[ #"orderID" ];
[orderMapping addAttributeMappingsFromArray:#[#"orderID", #"name"]];
RKRelationshipMapping *appCollectionRelationship = [RKRelationshipMapping relationshipMappingFromKeyPath:#"collections" toKeyPath:#"collections" withMapping:collectionMapping];
[appMapping addPropertyMapping:appCollectionRelationship];
RKRelationshipMapping *collectionFriendRelationship = [RKRelationshipMapping relationshipMappingFromKeyPath:#"friends" toKeyPath:#"friends" withMapping:friendMapping];
[collectionMapping addPropertyMapping:collectionFriendRelationship];
RKRelationshipMapping *friendsOrderRelationship = [RKRelationshipMapping relationshipMappingFromKeyPath:#"orders" toKeyPath:#"orders" withMapping:orderMapping];
[friendsOrderRelationship setAssignmentPolicy:RKUnionAssignmentPolicy];
[friendMapping addPropertyMapping:friendsOrderRelationship];
Then, querying the /test route on my API (which outputs the JSON block above) with the below console out...
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:appMapping method:RKRequestMethodAny pathPattern:nil keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptor:responseDescriptor];
[objectManager getObjectsAtPath:#"/test" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
App *app = [mappingResult firstObject];
for (Collection *collection in app.collections) {
NSLog(#"collectionID = %#", collection.collectionID);
for (Friend *friend in collection.friends) {
NSLog(#"friendID = %#", friend.friendID);
for (Order *order in friend.orders) {
NSLog(#"Name = %#", order.name);
}
}
}
} failure:^(RKObjectRequestOperation *operation, NSError *error) {}];
Provides the following output - notice both Name's are Medium, not one Small and the other Medium.
collectionID = 120
friendID = 6
Name = Medium
Name = Small
collectionID = 123
friendID = 6
Name = Medium
Name = Small
Why would this be happening? I'm going to guess that it's because both friend ID's are the same, even though they're under different collections...
This is because you have identificationAttributes = #[ #"friendID" ]; and the default relationship connection rule for relationships rather than replacement. So, the second mapping finds the friend created by the first mapping and replaces the order relationship contents.
You can't realistically change the unique identifier based on the JSON you show, but you might want to examine that (in relation to your object graph requirements).
Also / alternatively, see this answer for details of how to change the relationship assignment policy so that the original relationship contents aren't lost.

How do I loop through an NSArray of Custom Objects

I created an NSArray from a CoreData fetch like so:
self.farSiman = [self.managedObjectContext executeFetchRequest:request error:&error];
In a tableview I used this code to get my custom objects:
Holiday *holiday = [self.dates objectAtIndex:indexPath.row];
cell.nameLabel.text = holiday.name;
But Im now in another viewcontroller, trying to plot the data on a mapkit, so in the plotting method i originally did this because i was getting an array from a plist file. But now my array is of custom Holiday objects so this doesnt work anymore:
NSLog(#"dictionary is %#", self.farSiman);
for (NSDictionary * dict in self.farSiman) {
NSNumber * latitude = [dict objectForKey:#"latitude"];
NSNumber * longitude = [dict objectForKey:#"longitude"];
NSString * storeDescription = [dict objectForKey:#"name"];
NSString * address = [dict objectForKey:#"address"];
NSLog(#"logging location %#", storeDescription);
CLLocationCoordinate2D coordinate;
coordinate.latitude = latitude.doubleValue;
coordinate.longitude = longitude.doubleValue;
MyLocation *annotation = [[MyLocation alloc] initWithName:storeDescription address:address coordinate:coordinate];
[_mapView addAnnotation:annotation];
}
My dictionary log prints out this:
dictionary is (
"<Holiday: 0x838bc80> (entity: Holiday; id: 0x838ca60 <x-coredata://E41B0CCD-2F03-4C4F-B054-18537096771C/Holiday/p1> ; data: <fault>)",
"<Holiday: 0x838e330> (entity: Holiday; id: 0x838ca70 <x-coredata://E41B0CCD-2F03-4C4F-B054-18537096771C/Holiday/p2> ; data: <fault>)"
Which means its an array of holiday objects.
How do I get each object in my for loop since Im using enumeration instead of a traditional for i = 0; i<count; i++?
It looks like you are using a custom object with CoreData, so it will be returning an array of your class.
Does this work:
for (Holiday *holiday in self.farSiman) {
// your code here
// [holiday foo]
}
If CoreData is not using your custom object, it will return an array of NSManagedObject, in which case use this:
for (NSManagedObject *holiday in self.farSiman) {
// your code here
//[holiday valueForKey:#"foo"]
}

NSManagedObject to NSDictionary

Trying to serialise NSManagedObject to NSDictionary including related data.
I found some code for that here:
http://vladimir.zardina.org/2010/03/serializing-archivingunarchiving-an-nsmanagedobject-graph/
Unfortunately, there is no support for NSOrderedSet. Tried to implement it myself, but have a crash with message doesn't recognise selector on line if (!relatedObject.traversed) {.
- (NSDictionary*) toDictionary
{
self.traversed = YES;
NSArray* attributes = [[[self entity] attributesByName] allKeys];
NSArray* relationships = [[[self entity] relationshipsByName] allKeys];
NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithCapacity:
[attributes count] + [relationships count] + 1];
[dict setObject:[[self class] description] forKey:#"class"];
for (NSString* attr in attributes) {
NSObject* value = [self valueForKey:attr];
if (value != nil) {
[dict setObject:value forKey:attr];
}
}
for (NSString* relationship in relationships) {
NSObject* value = [self valueForKey:relationship];
if ([value isKindOfClass:[NSSet class]]) {
// To-many relationship
// The core data set holds a collection of managed objects
NSSet* relatedObjects = (NSSet*) value;
// Our set holds a collection of dictionaries
NSMutableSet* dictSet = [NSMutableSet setWithCapacity:[relatedObjects count]];
for (ExtendedManagedObject* relatedObject in relatedObjects) {
if (!relatedObject.traversed) {
[dictSet addObject:[relatedObject toDictionary]];
}
}
[dict setObject:dictSet forKey:relationship];
}
else if ([value isKindOfClass:[NSOrderedSet class]]) {
// To-many relationship
// The core data set holds a collection of managed objects
NSOrderedSet* relatedObjects = (NSOrderedSet *)value;
// Our set holds a collection of dictionaries
NSMutableSet* dictSet = [NSMutableSet setWithCapacity:[relatedObjects count]];
for (ExtendedManagedObject* relatedObject in relatedObjects) {
if (!relatedObject.traversed) {
[dictSet addObject:[relatedObject toDictionary]];
}
}
[dict setObject:dictSet forKey:relationship];
}
else if ([value isKindOfClass:[ExtendedManagedObject class]]) {
// To-one relationship
ExtendedManagedObject* relatedObject = (ExtendedManagedObject*) value;
if (!relatedObject.traversed) {
// Call toDictionary on the referenced object and put the result back into our dictionary.
[dict setObject:[relatedObject toDictionary] forKey:relationship];
}
}
}
return dict;
}
- (void) populateFromDictionary:(NSDictionary*)dict
{
NSManagedObjectContext* context = [self managedObjectContext];
for (NSString* key in dict) {
if ([key isEqualToString:#"class"]) {
continue;
}
NSObject* value = [dict objectForKey:key];
if ([value isKindOfClass:[NSDictionary class]]) {
// This is a to-one relationship
ExtendedManagedObject* relatedObject =
[ExtendedManagedObject createManagedObjectFromDictionary:(NSDictionary*)value
inContext:context];
[self setValue:relatedObject forKey:key];
}
else if ([value isKindOfClass:[NSSet class]]) {
// This is a to-many relationship
NSSet* relatedObjectDictionaries = (NSSet*) value;
// Get a proxy set that represents the relationship, and add related objects to it.
// (Note: this is provided by Core Data)
NSMutableSet* relatedObjects = [self mutableSetValueForKey:key];
for (NSDictionary* relatedObjectDict in relatedObjectDictionaries) {
ExtendedManagedObject* relatedObject =
[ExtendedManagedObject createManagedObjectFromDictionary:relatedObjectDict
inContext:context];
[relatedObjects addObject:relatedObject];
}
}
else if (value != nil) {
// This is an attribute
[self setValue:value forKey:key];
}
}
}
it is fast and easy way
NSMutableArray *array = [NSMutableArray arrayWithCapacity:ManagedObjectItems.count];
[[ManagedObjectItems allObjects] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
Diary_item_food *food = obj;
NSArray *keys = [[[food entity] attributesByName] allKeys];
NSDictionary *dict = [obj dictionaryWithValuesForKeys:keys];
[array addObject:dict];
}];
I found the ready gist on Gihub: https://gist.github.com/nuthatch/5607405
Even easier way, query for the objectID and use NSDictionaryResultType on the fetch request.
Update: Only if you don't need related data.