RKObjectMapping connections - ios

I have neŃ…t response:
{
"orders": [
{
"date": "2013-11-18T13:00:39.000Z",
"state": "new",
"id": 11,
"order_item_ids": [
27,
28
]
}
],
"order_items": [
{
"count": 2,
"id": 27,
"item_id": 1,
"order_id": 11
},
{
"count": 1,
"id": 28,
"item_id": 2,
"order_id": 11
}
]
}
And next object I want to map it:
#interface Order : NSObject
#property (nonatomic, strong) NSString* state;
#property (nonatomic, strong) NSMutableArray* orderItems;
#property (nonatomic, strong) NSDate* visitDate;
#end
orderItems should be array of:
#interface OrderItem : NSObject
#property (nonatomic) NSUInteger ID;
#property (nonatomic) NSUInteger itemID;
#property (nonatomic) NSUInteger count;
#end
In case of RKEntityMapping I should use addConnectionForRelationship to map order_item_ids to orderItems array via order_items response. But what should I do to do to connect it in case of RKObjectMapping? Off course I can map both orders and orderItems with separate response descriptors and than parse it, but I want to make RestKit do it for me. Another idea is to use CoreData and RKEntityMapping but I'm not sure that I want in this case, it will be overkill in this case.

You can't have it done automatically with RKObjectMapping because you can't index into both arrays in any way. The entity mapping only works because you can use foreign key mapping after both mappings have been performed. You can duplicate this by using multiple response descriptors and mappings and then combining the contents of the mapping result.

Related

Mapping Firebase lists to objects with Mantle

I'm looking for an effective way to map Firebase lists to custom Objective-C objects with Mantle. Firebase doesn't really have a concept of arrays, so every item in a list has an explicit id. A list in Firebase of type event appears something like this:
"events": {
"id0": {
"name": "Event 1",
"date": "2017-03-28T08:00:00+00:00",
"venue": "Venue 1"
},
"id1": {
"name": "Event 2",
"date": "2017-03-29T08:00:00+00:00",
"venue": "Venue 2"
},
...
"id5": {
"name": "Event 6",
"date": "2017-04-05T08:00:00+00:00",
"venue": "Venue 6"
}
}
In Objective-C terms, this translates perfectly well into an NSDictionary, but it's easier to work with an NSArray in my UITableViewController. At the moment I'm using NSDictionary for each event, and simply moving the event's key inside the Dictionary and creating an array of dictionaries as follows (feel free to comment on whether this is a good or bad idea):
{
"eventId": "id0",
"name": "Event 1",
"date": "2017-03-28T08:00:00+00:00",
"venue": "Venue 1"
},
{
"eventId": "id1",
"name": "Event 2",
"date": "2017-03-29T08:00:00+00:00",
"venue": "Venue 2"
}
...
My idea is to then create an Event object:
#interface Event: NSObject <MLTModel>
#property (nonatomic, strong) NSString *eventId;
#property (nonatomic, strong) NSString *name;
#property (nonatomic, strong) NSDate *date;
#property (nonatomic, strong) NSString *venue;
#end
So my question is this: is there a way to map this Firebase list to a custom Event object with Mantle so that it can be easily used for UITableViews in iOS? Specifically, how does one map the key (id0, id1 etc) of the Firebase event to be a property of the custom Event object, and back again? Or would it be better to move the key "manually" and then hand over to Mantle?

Restkit fails to use appropriate Mapping

I am using Restkit 0.26.0 to map JSON with multiple layers and this key path causes trouble:
productColorImages: [
{
id: 10,
productId: "232",
color: "green",
url: "exampleURL.com"
},
{
id: 11,
productId: "232",
color: "red",
url: "exampleURL.com"
},
{
id: 12,
productId: "232",
color: "blue",
url: "exampleURL.com"
}
],
My mapping
RKObjectMapping *transferProductColorImageMapping = [RKObjectMapping mappingForClass:[TransferProductColorImage class]];
[transferProductColorImageMapping addAttributeMappingsFromDictionary:#{ #"identifier" : #"id",
#"URL" : #"URL",
#"productId" : #"productId",
#"color" : #"color"
[getProductPageMapping addRelationshipMappingWithSourceKeyPath:#"productColorImages" mapping:transferProductColorImageMapping];
The destination class
#interface TransferProductColorImage : NSObject
#property (nonatomic) NSNumber * identifier; // Mapps to id ;int 64
#property (nonatomic) NSString * URL;
#property (nonatomic) NSString * productId;
#property (nonatomic) NSString * color;
The problem is that the resulting Object is of the right class but the properties are not filled.
I searched in the logs and found this:
2016-05-30 17:42:24.007 thebrandsapp[20697:7047455] T restkit.object_mapping:RKMappingOperation.m:1173 Performing mapping operation: <RKMappingOperation 0x7f9e3b9c11e0> for 'TransferProductColorImage' object. Mapping values from object {
color = green;
id = 10;
productId = 232;
url = "exampleURL.com";
} to object <TransferProductColorImage: 0x7f9e3b846d60> with object mapping (null)
On the Model Object the productColorImages is a NSArray:
#property (nonatomic) NSArray <TransferProductColorImage *> * productColorImages;
This is odd because. How does Restkit know what mapping to use if the mapping is null. Is there any reason why the object mapping could be set to null?
Update: I found that the identifier and the color property were mapped correctly it is, the identifier and the url are not mapped correctly.
I changed the URL property to be lowercase: "url" and mapped it exactly like I did before and it worked. It seems like the the all-caps property name threw RestKit of.
I swooped identifier and id, now they map correctly as well.

Mapping nested nodes with RESTKit

I am implementing a simple iOS app that consumes reddit feeds and I need to show the feed comments. I am using RESTKit to do the ORM part and I am having an annoyance with mapping the comments.
Reddit comments can have replies and the replies can have replies and so on. It's a tree of comments. This is no big deal. The problem is that among the children nodes there might exist one that's different (used to sign that there are more comments to load on that particular thread).
Here's a JSON example:
"replies":{
"data":
{
"after": null,
"before": null,
"children":
[
{
"data":
{
"approved_by": null,
"author": "InstaRamen",
"author_flair_css_class": null,
"author_flair_text": null,
"banned_by": null,
"body": "that or he counted it all wrong",
"body_html":"<div class=\"md\"><p>that or he counted it all wrong</p>\n</div>",
"controversiality": 0,
"created": 1405093511,
"created_utc": 1405064711,
"distinguished": null,
"downs": 0,
"edited": false,
"gilded": 0
"id": "ciubfdk",
"likes": null,
"link_id": "t3_2ae8oa",
"name": "t1_ciubfdk",
"num_reports": null,
"parent_id": "t1_ciuao19",
"replies":
{
"data":
{
"after": null,
"before": null,
"children":
[
{
"data":
{
"approved_by": null,
"author": "Everlasting",
"author_flair_css_class": null,
"author_flair_text": null,
"banned_by": null,
"body": "3, sir.",
"body_html":"<div class=\"md\"><p>3, sir.</p>\n</div>",
"controversiality": 0,
"created": 1405093986,
"created_utc": 1405065186,
"distinguished": null,
"downs": 0,
"edited": false,
"gilded": 0,
"id": "ciubj8h",
"likes": null,
"link_id": "t3_2ae8oa",
"name": "t1_ciubj8h",
"num_reports": null,
"parent_id": "t1_ciubfdk",
"replies":
{
"data":
{
"after": null,
"before": null,
"children":
[
{
"data":
{
"children":
[
"ciublgq"
],
"count": 0,
"id":"ciublgq",
"name":"t1_ciublgq",
"parent_id": "t1_ciubj8h"
},
"kind": "more"
}
],
"modhash": ""
},
"kind": "Listing"
},...
So, you can see that the typical comment replies has a structure of
replies
|-data
|-children
|-data
|- #comment key/values
But the "Load more" item has a structure of:
replies
|-data
|-children
|-data
|-children
|-more loading info...
My DataObject is:
#interface RedditComment : NSObject
#property (nonatomic, copy) NSString *author;
#property (nonatomic, readwrite) NSInteger createdUTC;
#property (nonatomic, readwrite) NSInteger score;
#property (nonatomic, copy) NSString *subreddit;
#property (nonatomic, copy) NSString *subredditId;
#property (nonatomic, readwrite) NSInteger ups;
#property (nonatomic, copy) NSString *body;
#property (nonatomic, copy) NSArray *replies;
+ (RKObjectMapping*)restKitMapping;
#end
And my mapping implementation is:
#implementation RedditComment
+ (RKObjectMapping*)restKitMapping;
{
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[self class]];
[mapping addAttributeMappingsFromDictionary:#{
#"author": #"author",
#"created_utc": #"createdUTC",
#"score": #"score",
#"subreddit": #"subreddit",
#"subreddit_id": #"subredditId",
#"body": #"body",
#"ups": #"ups",
}];
[mapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"replies" toKeyPath:#"replies" withMapping:mapping]];
return mapping;
}
#end
This yields an error:
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<__NSCFConstantString 0x1daa0d0> valueForUndefinedKey:]: this class is not key value coding-compliant for the key body.'
This is because the "Load more" node doesn't have a body key (it's missing other keys as well, but this is the first one that RESTKit complains about).
I've tried to use a dynamic mapping block to change the mapping in runtime but couldn't figure out how to isolate the problematic node (i.e. the "Load More").
My question is how can I distinguish between both possible values in "replies" JSON nodes?
Can I have two mappings for the same partial key?
That is a mapping for replies > data > children > data and another to replies > data > children > data > children. If so how to do it?
Or is there anything wrong in my line of thinking?
Thanks in advance to everyone that is able to help or that simply take the time to read... ;-)
EDIT
including response descriptor
As requested on comments here's the RKResponseDescriptor:
RKResponseDescriptor *commentResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:[PPRedditComment restKitMapping]
method:RKRequestMethodAny
pathPattern:nil
keyPath:#"data"
statusCodes:nil];
EDIT #2
including a "good comment" node to show what I am interested to map to my DO
"data":
{
"approved_by": null,
"author": "PerimeterBlue",
"author_flair_css_class":null,
"author_flair_text":null,
"banned_by":null,
"body": "This actually looks really cool.",
"body_html": "<div class=\"md\"><p>This actually looks really cool.</p>\n</div>",
"controversiality":0,
"created": 1405095679,
"created_utc": 1405066879,
"distinguished":null,
"downs": 0,
"edited": false,
"gilded": 0,
"id": "ciubwld",
"likes":null,
"link_id": "t3_2aelr4",
"name": "t1_ciubwld",
"num_reports": null,
"parent_id": "t3_2aelr4",
"replies": {...},
"saved": false,
"score": 48,
"score_hidden": false,
"subreddit": "gaming",
"subreddit_id": "t5_2qh03",
"ups": 48
}
Ok, apparently got the issue. Wain was right: it was a path related issue.
There were certain nodes which was supposed to contain dictionaries and sometimes contained strings. In the way the paths were defined this caused an non-existent selector (the property from DO) to be called on the NSString class. Since it didn't contain it, it went BOOM!
Mapping was corrected to this:
+ (RKObjectMapping*)restKitMapping;
{
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[self class]];
[mapping addAttributeMappingsFromDictionary:#{
#"author": #"author",
#"created_utc": #"createdUTC",
#"score": #"score",
#"subreddit": #"subreddit",
#"subreddit_id": #"subredditId",
#"body": #"body",
#"ups": #"ups",
}];
RKDynamicMapping *dynamicMapping = [RKDynamicMapping new];
[dynamicMapping setObjectMappingForRepresentationBlock:^RKObjectMapping *(id representation) {
RKObjectMapping *result = mapping;
// When node is empty string don't map...
if([representation isKindOfClass:[NSString class]] && [representation isEqualToString:#""])
{
result = nil;
}
return result;
}];
[mapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"replies"
toKeyPath:#"replies"
withMapping:dynamicMapping]];
return mapping;
}
Response descriptor corrected to:
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:[PPRedditFeedCollection restKitMapping]
method:RKRequestMethodAny
pathPattern:nil
keyPath:#"data"
statusCodes:nil];
Hope this helps someone! :-)

Parsing JSON in iOS, inner dictionaries

I am new to JSON and iOS, I have some json data that I want to load into an array,
This is the json data
{
"items": [
{
"name": "abc",
"description": "cheeseburger",
},
{
"name": "def",
"description": "ostrichburger",
},
{
"name": "zxc",
"description": "sh1tjustgotreal",
},
{
"name": "scfs",
"description": "mylifeforaiur",
}
]
}
Now I keep on getting dictionaries with dictionaries? Why is that?
On another note, If I can modify the structure of this json cos I really just want to access the inner nodes ( abc, def ) what would I change in it to make it simpler for me and others to use it? Can I get rid of the "items" node?
When the data on the sender's end is structured as a collection of named fields, you get back a dictionary for each group of named fields. In this particular case, it looks like the outermost object has one field, which is a collection (i.e. a list or an array) of objects that each have a name and description. In other words, the sender sends you something like this:
#interface Item
#property (readwrite, copy) NSString* name;
#property (readwrite, copy) NSString* description;
#end
#interface MyObject
// This array contains objects of type Item
#property (readwrite) NSArray *items;
#end

Did not find mappable relationship value keyPath with RestKit

I'm unable to map my JSON to Core Data with RestKit. RK logging is saying:
Did not find mappable relationship value keyPath.
I'm clearly doing something wrong.
Log:
2012-11-20 15:40:36.525 eMobile[44849:12603] T restkit.object_mapping:RKObjectMappingOperation.m:342 Mapping attribute value keyPath 'OBJECTCLASS' to 'type'
2012-11-20 15:40:36.525 eMobile[44849:12603] T restkit.object_mapping:RKObjectMappingOperation.m:359 Skipped mapping of attribute value from keyPath 'OBJECTCLASS to keyPath 'type' -- value is unchanged (SFAACCOUNT)
2012-11-20 15:40:36.525 eMobile[44849:12603] T restkit.object_mapping:RKObjectMappingOperation.m:342 Mapping attribute value keyPath 'TIMESTAMP' to 'timestamp'
2012-11-20 15:40:36.526 eMobile[44849:12603] T restkit.object_mapping:RKObjectMappingOperation.m:352 Mapped attribute value from keyPath 'TIMESTAMP' to 'timestamp'. Value: 2012-11-20T15:40:08Z
2012-11-20 15:40:36.526 eMobile[44849:12603] D restkit.object_mapping:RKObjectMappingOperation.m:483 Did not find mappable relationship value keyPath 'VALUES'
2012-11-20 15:40:36.526 eMobile[44849:12603] D restkit.object_mapping:RKObjectMappingOperation.m:662 Finished mapping operation successfully...
2012-11-20 15:40:36.526 eMobile[44849:12603] T restkit.object_mapping:RKObjectMapper.m:293 Examining keyPath 'VALUES' for mappable content...
2012-11-20 15:40:36.527 eMobile[44849:12603] D restkit.object_mapping:RKObjectMapper.m:303 Found unmappable value at keyPath: VALUES
2012-11-20 15:40:36.527 eMobile[44849:12603] D restkit.object_mapping:RKObjectMapper.m:367 The following operations are in the queue: (
)
2012-11-20 15:40:36.527 eMobile[44849:12603] D restkit.object_mapping:RKObjectMapper.m:382 Finished performing object mapping. Results: {
JSONDATA = (
"<EntityClass: 0x127da9e0> (entity: EntityClass; id: 0x127527d0 <x-coredata://E20EA003-1D5A-4B27-92DF-DFD8BE7074CB/EntityClass/p2> ; data: {\n timestamp = \"2012-11-20T15:40:08Z\";\n type = VCFUND;\n values = \"<relationship fault: 0x12703200 'values'>\";\n})",
"<EntityClass: 0x127da6b0> (entity: EntityClass; id: 0x12695dc0 <x-coredata://E20EA003-1D5A-4B27-92DF-DFD8BE7074CB/EntityClass/p1> ; data: {\n timestamp = \"2012-11-20T15:40:08Z\";\n type = SFAACCOUNT;\n values = \"<relationship fault: 0x11efb910 'values'>\";\n})"
);
}
2012-11-20 15:40:36.530 eMobile[44849:11603] (
"<EntityClass: 0x126b7400> (entity: EntityClass; id: 0x127527d0 <x-coredata://E20EA003-1D5A-4B27-92DF-DFD8BE7074CB/EntityClass/p2> ; data: <fault>)",
"<EntityClass: 0x127de030> (entity: EntityClass; id: 0x12695dc0 <x-coredata://E20EA003-1D5A-4B27-92DF-DFD8BE7074CB/EntityClass/p1> ; data: <fault>)"
)
JSON sample:
{
"JSONDATA": [
{
"OBJECTCLASS": "VCFUND",
"DESCRIPTION": {
"VALUES": [
{
"DATA": [
{
"NAME": "Fund 1",
"VALUE": "Buyout Fund 1"
},
{
"NAME": "Fund 2",
"VALUE": "Buyout Fund 2"
},
{
"NAME": "Fund 3",
"VALUE": "Buyout Fund 3"
}
],
"IQID": "059386B4D26249358C68E978D3C10C84"
}
]
},
"TIMESTAMP": "2012-11-17T22:03:55Z"
},
{
"OBJECTCLASS": "PROPERTIES",
"DESCRIPTION": {
"VALUES": [
{
"DATA": [
{
"NAME": "Property 1",
"VALUE": "Buyout Property 1"
},
{
"NAME": "Property 2",
"VALUE": "Buyout Property 2"
},
{
"NAME": "Property 3",
"VALUE": "Buyout Property 3"
}
],
"IQID": "456789087654678909876589098"
}
]
},
"TIMESTAMP": "2012-11-17T22:03:55Z"
}
]
}
Here is my mapping:
objectManager.objectStore = [RKManagedObjectStore objectStoreWithStoreFilename:#"AppCD.sqlite"];
RKManagedObjectMapping* entityClassItemValueMapping = [RKManagedObjectMapping mappingForClass:[EntityClassItemValue class] inManagedObjectStore:[[RKObjectManager sharedManager] objectStore]];
entityClassItemValueMapping.primaryKeyAttribute = #"name";
[entityClassItemValueMapping mapKeyPath:#"NAME" toAttribute:#"name"];
[entityClassItemValueMapping mapKeyPath:#"VALUE" toAttribute:#"value"];
RKManagedObjectMapping* entityClassItemMapping = [RKManagedObjectMapping mappingForClass:[EntityClassItem class] inManagedObjectStore:[[RKObjectManager sharedManager] objectStore]];
entityClassItemMapping.primaryKeyAttribute = #"iqid";
[entityClassItemMapping mapKeyPath:#"IQID" toAttribute:#"iqid"];
RKManagedObjectMapping* entityClassMapping = [RKManagedObjectMapping mappingForClass:[EntityClass class] inManagedObjectStore:[[RKObjectManager sharedManager] objectStore]];
entityClassMapping.primaryKeyAttribute = #"type";
[entityClassMapping mapKeyPath:#"OBJECTCLASS" toAttribute:#"type"];
[entityClassMapping mapKeyPath:#"TIMESTAMP" toAttribute:#"timestamp"];
[entityClassMapping mapKeyPath:#"VALUES" toRelationship:#"values" withMapping:entityClassItemMapping];
[entityClassItemMapping mapKeyPath:#"DATA" toRelationship:#"entityClassItemValues" withMapping:entityClassItemValueMapping];
[[RKObjectManager sharedManager].mappingProvider setMapping:entityClassMapping forKeyPath:#"JSONDATA"];
[[RKObjectManager sharedManager].mappingProvider setMapping:entityClassItemMapping forKeyPath:#"VALUES"];
[[RKObjectManager sharedManager].mappingProvider setMapping:entityClassItemValueMapping forKeyPath:#"DATA"];
[[objectManager client] setValue:[cookies objectForKey:#"Cookie"] forHTTPHeaderField:#"Cookie"];
NSDictionary *params = [[NSDictionary alloc] initWithObjectsAndKeys:query, #"Targ", #"", #"Query", #"F285E67F2C8CEC9837B68", #"$SESSION", #"3A7037FA806156C4", #"VERSION", nil];
[objectManager loadObjectsAtResourcePath:URL_DATA usingBlock:^(RKObjectLoader *loader) {
loader.method = RKRequestMethodPOST;
loader.params = params;
loader.mappingProvider = [RKObjectManager sharedManager].mappingProvider;
loader.onDidLoadObjects = loadBlock;
loader.onDidFailWithError = failBlock;
}];
My CoreData models are:
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#class EntityClassItem;
#interface EntityClass : NSManagedObject
#property (nonatomic, retain) NSString * timestamp;
#property (nonatomic, retain) NSString * type;
#property (nonatomic, retain) NSSet *values;
#end
#interface EntityClass (CoreDataGeneratedAccessors)
- (void)addValuesObject:(EntityClassItem *)value;
- (void)removeValuesObject:(EntityClassItem *)value;
- (void)addValues:(NSSet *)values;
- (void)removeValues:(NSSet *)values;
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#class EntityClassItem;
#interface EntityClass : NSManagedObject
#property (nonatomic, retain) NSString * timestamp;
#property (nonatomic, retain) NSString * type;
#property (nonatomic, retain) NSSet *values;
#end
#interface EntityClass (CoreDataGeneratedAccessors)
- (void)addValuesObject:(EntityClassItem *)value;
- (void)removeValuesObject:(EntityClassItem *)value;
- (void)addValues:(NSSet *)values;
- (void)removeValues:(NSSet *)values;
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#class EntityClassItem;
#interface EntityClassItemValue : NSManagedObject
#property (nonatomic, retain) NSString * name;
#property (nonatomic, retain) NSString * value;
#property (nonatomic, retain) EntityClassItem *entityClassItem;
I figured it out after a lot of debugging and re-reading of the docs. It was a lack of understanding on my part around:
mapKeyPath:#"foo" toRelationship:#"bar" withMapping:fooBar];
As the docs clearly state, mapKeyPath must match both the property of the object you are mapping to in addition to the node in the JSON payload. My payload's case didn't match so it was failing.

Resources