OliverLetterer / SLRESTfulCoreData

Objc naming conventions, autogenerated accessors at runtime, URL substitutions and intelligent attribute mapping
MIT License
178 stars 13 forks source link

API with curious JSON structure #12

Closed tomburns closed 10 years ago

tomburns commented 10 years ago

I'm hoping to use SLRESTfulCoreData with the API my app makes use of, but their JSON structure is a bit unorthodox. In the example below, I'm hoping to create a managed object of type MNOCurrentUser using the contents of the user hash which is returned:

2013-12-09 10:05:35.588 Merino[2023:70b] Error Domain=SLRESTfulCoreDataErrorDomain Code=1 "API return an unexpected JSON object ({
    user =     {
        id = 1887075;
        "photo_url" = "http://avatars.ravelry.com/tomburns/173709885/george-iawl_large.jpg";
        "small_photo_url" = "http://avatars.ravelry.com/tomburns/173709885/george-iawl_small.jpg";
        "tiny_photo_url" = "http://avatars.ravelry.com/tomburns/173709885/george-iawl_tiny.jpg";
        username = tomburns;
    };
}) which cannot be converted into an NSManagedObject from URL https://api.ravelry.com/current_user.json." UserInfo=0x8d18c10 {NSLocalizedDescription=API return an unexpected JSON object ({
    user =     {
        id = 1887075;
        "photo_url" = "http://avatars.ravelry.com/tomburns/173709885/george-iawl_large.jpg";
        "small_photo_url" = "http://avatars.ravelry.com/tomburns/173709885/george-iawl_small.jpg";
        "tiny_photo_url" = "http://avatars.ravelry.com/tomburns/173709885/george-iawl_tiny.jpg";
        username = tomburns;
    };
}) which cannot be converted into an NSManagedObject from URL https://api.ravelry.com/current_user.json.}

Is there a way to tell SLRESTfulCoreData to traverse the returned JSON and use the contents of user to populate the managed object?

OliverLetterer commented 10 years ago

you can overwrite -[NSManagedObject updateWithRawJSONDictionary:] like

- (void)updateWithRawJSONDictionary:(NSDictionary *)d
{
    [super updateWithRawJSONDictionary:d[@"user"]];
}
tomburns commented 10 years ago

Thanks! When I overwrite that method, I don't see it getting called; if I set a breakpoint on the call to super it's never hit. Is it possible that using mogenerator is causing trouble?

OliverLetterer commented 10 years ago

ive never worked with mogenerator, so i cant tell.

tomburns commented 10 years ago

Here is the code I'm using to attempt to fetch the object (I am currently doing this in my AppDelegate for debug purposes):

        NSURL *currentUserURL = [NSURL URLWithString:@"https://api.ravelry.com/current_user.json"];

        [MNOCurrentUser fetchObjectFromURL:currentUserURL completionHandler:^(id fetchedObject, NSError *error) {
            NSLog(@"%@",fetchedObject);
            NSLog(@"%@",error);
        }];

As far as I can tell, -updateWithRawJSONDictionary: is not being called by this code, though by setting a breakpoint I can see +initialize getting called on MNOCurrentUser. I will continue attempting to get this working, but if you have any suggestions I would certainly appreciate it!

edit: Here's the complete console output:

2013-12-09 11:38:29.310 Merino[6045:1303] WARNING: JSON Object did not have an id ({
    user =     {
        id = 1887075;
        "photo_url" = "http://avatars.ravelry.com/tomburns/173709885/george-iawl_large.jpg";
        "small_photo_url" = "http://avatars.ravelry.com/tomburns/173709885/george-iawl_small.jpg";
        "tiny_photo_url" = "http://avatars.ravelry.com/tomburns/173709885/george-iawl_tiny.jpg";
        username = tomburns;
    };
})
2013-12-09 11:38:29.311 Merino[6045:70b] (null)
2013-12-09 11:38:29.311 Merino[6045:70b] Error Domain=SLRESTfulCoreDataErrorDomain Code=1 "API return an unexpected JSON object ({
    user =     {
        id = 1887075;
        "photo_url" = "http://avatars.ravelry.com/tomburns/173709885/george-iawl_large.jpg";
        "small_photo_url" = "http://avatars.ravelry.com/tomburns/173709885/george-iawl_small.jpg";
        "tiny_photo_url" = "http://avatars.ravelry.com/tomburns/173709885/george-iawl_tiny.jpg";
        username = tomburns;
    };
}) which cannot be converted into an NSManagedObject from URL https://api.ravelry.com/current_user.json." UserInfo=0xa25cfc0 {NSLocalizedDescription=API return an unexpected JSON object ({
    user =     {
        id = 1887075;
        "photo_url" = "http://avatars.ravelry.com/tomburns/173709885/george-iawl_large.jpg";
        "small_photo_url" = "http://avatars.ravelry.com/tomburns/173709885/george-iawl_small.jpg";
        "tiny_photo_url" = "http://avatars.ravelry.com/tomburns/173709885/george-iawl_tiny.jpg";
        username = tomburns;
    };
}) which cannot be converted into an NSManagedObject from URL https://api.ravelry.com/current_user.json.}
tomburns commented 10 years ago

I was able to get this working by overwriting + (instancetype)updatedObjectWithRawJSONDictionary:inManagedObjectContext: like so:

+ (instancetype)updatedObjectWithRawJSONDictionary:(NSDictionary *)rawDictionary inManagedObjectContext:(NSManagedObjectContext *)context
{
    return [super updatedObjectWithRawJSONDictionary:rawDictionary[@"user"]
                              inManagedObjectContext:context];
}

Thanks for your help!

itsthejb commented 10 years ago

I also had to work around a similar issue with an API that always returns the JSON objects "wrapped" in a parent tag. Got this working fine, but now running up against the issue that array requests return a pluralised parent key. Eg., a request for a single user returns a dictionary with the representation inside the value of the "user" key, but a request for an array of users returns the JSON array as the value of the key "users".

I'm sure I can work around this somehow, but under the current implementation this isn't so obvious or easy. Perhaps, then, some sort of the provision for this is worth considering in a future version? If so, happy to help out.

OliverLetterer commented 10 years ago

@itsthejb do i understand this correctly? you have a route /users which returns a json like

"users": [ {}, {}, {} ]

and you want to use fetchObjectsFromURL:completionHandler: or fetchObjectsForRelationship:fromURL:completionHandler:?

itsthejb commented 10 years ago

Hey Oliver,

More or less. Specifically, I want to perform a non-primary request that returns an array of course objects, let's say it's something like -coursesBetweenDate:andDate, and the JSON response returns a dictionary with the array as the value of the courses key, so using those methods directly (as I want to) isn't currently possible. So it's:

"courses": [ {}, {}, {} ]

Coincidentally enough, for direct requests I have the same issue as @tomburns - requesting a course by id (for example) returns the course object as the value of a parent course property... Agree that this is redundant stuff, but I'm working with an existing API that's not likely to change so fundamentally right now.

"course": {}

Currently I'm actually using AFNetworking directly for my array requests, and more or less re-implementing a cut-down version of the fetch methods myself. This is actually fine enough for my needs (I think), but if you were interested in expanding the flexibility of the library then perhaps responses could provide some kind of unwrapping hook method. Perhaps, even, an optional block on the request itself? An API something like:

+ (void)fetchObjectsFromURL:(NSURL *)URL
         responseFormatter:(id (^)(id JSONObject)) formatter
         completionHandler:(void(^)(id fetchedObject, NSError *error))completionHandler

That way, when making a normal request you could do:

  [self fetchObjectsFromURL:[NSURL URLWithString:@"some/url"]
             responseFormatter:^id (id JSONObject)
  {
    return JSONObject[@"course"];
  } completionHandler:^(id fetchedObject, NSError *error) {
    ...
  }];

...and for my array example:

  [self fetchObjectsFromURL:[NSURL URLWithString:@"some/url"]
             responseFormatter:^id (id JSONObject)
  {
    return JSONObject[@"courses"];
  } completionHandler:^(id fetchedObject, NSError *error) {
    ...
  }];

Basically providing a hook to reformat the response in a way that the library itself expects.

What do you think? I'd be happy to do an implementation of this and a pull request this week if you'd like. I understand you want to keep the library itself as simple as possible, but can't hurt to make these kinds of customisations possible for users to adapt the library to their (idiosyncratic) APIs.

Thanks!

OliverLetterer commented 10 years ago

@itsthejb i decided to take a slightly different approach:

@optional
- (void)registerResponseObjectTransformerForNextRequest:(SLRESTfulCoreDataBackgroundQueueObjectTransformer)responseObjectTransformer;
- (void)registerRequestObjectTransformerForNextRequest:(SLRESTfulCoreDataBackgroundQueueObjectTransformer)responseObjectTransformer;

which must handle these kinds of transformations in a thread safe manner and are totally opt-in.

Feel free to give it a try, version 1.5.0 is currently pushed to https://github.com/Sparrow-Labs/Specs.

itsthejb commented 10 years ago

Hey Oliver,

This sounds great! I'll try these out right away and let you know if I have any feedback.

Thanks,

J