1
votes

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": "&lt;div class=\"md\"&gt;&lt;p&gt;This actually looks really cool.&lt;/p&gt;\n&lt;/div&gt;",
"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
}
1
The exception is raised on a string, so it is more likely an issue with your path navigation than the missing data (which would just be skipped). A dynamic mapping can allow you to analyse the data to choose what to do (i.e. the dictionary contents) but that does not seem like your issue here... You will need to show more information about your mapping and response descriptor structure (and how you're sure it is the 'load more' that is the culprit).Wain
Hi Wain, thanks a lot for your input. I will edit the question to include the response descriptor structure. The mapping is as shown in the original question. As for knowing that the culprit is the "load more", well I passed many hours debugging and logging the method. :-PDiesel Heart
Why do I not see children or data in your mapping if that is the only mapping?Wain
Ok, I'll try to make sense (I started with RK a couple of days ago and I might not have the picture totally clear): a "good" comment is the key collection that starts with approved_by and ups (see edit #2). Inside replies you have more comments (data blocks with approved_by and ups) aggregated under a children node which is itself under another (different) data node. Phew! It's some sort of whack JSON structure because data can mean different things depending on the context. That's why I am having a hard time with this.Diesel Heart

1 Answers

0
votes

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! :-)