Question

It seems here they do a workaround: Relationship between RKObjectMapping and RKEntityMapping

Can I mix them without doing any workaround? Thanks.

EDIT: Here I add a sample JSON. What I want to store in Core Data are the two entities Region, but nothing about the resultCode or resultDescription. That's the reason I ask if I can mix them.

{ "resultCode": 0, "resultDescription": "OK", "resultContent": [ { "region_id": 0, "description": "USA" }, { "region_id": 1, "description": "Europe" } ] }

Was it helpful?

Solution

I encountered the same problem myself when i worked with a legacy api. Here are my solutions:

Response json:

{
    "resultCode": 0,
    "resultDescription": "OK",
    "resultContent": [
        {
            "region_id": 0,
            "description": "USA"
        },
        {
            "region_id": 1,
            "description": "Europe"
        }
    ]
}

There are two solutions for this problem:

Solution #1: using object mapping with @"" key path

Suppose you already have the managed class Region with attribute names same as json key names

@interface Response 
@property (nonatomic) NSInteger resultCode;
@property (nonatomic, strong) NSString resultDescription;
@end 


@implementation Response
// Make it KV compliant 
@end

Mapping

// Response map
RKObjectMapping*  resMap = [RKObjectMapping mappingForClass: [Response class]];
[resMap addAttributeMappingsFromArray: @[@"resultCode", @"resultDescription"]];

responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:resMap method: RKRequestMethodAny pathPattern:@"" keyPath:@"" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];

// Region map
RKEntityMapping* mapping  =  [RKEntityMapping mappingForEntityForName: name inManagedObjectStore:[[RKObjectManager sharedManager] managedObjectStore]];

[mapping addAttributeMappingsFromArray: @[@"region_id", @"description"]];
descriptor = [RKResponseDescriptor responseDescriptorWithMapping:mapping method: RKRequestMethodAny pathPattern:@"/api/regions" keyPath:@"resultContent" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];

Get the result

Response* res = [[mappingResult dictionary] objectForKey:[NSNull null]];
NSArray* reg =  [[mappingResult dictionary] objectForKey:@"resultContent"];

Solution #2: Using serialization

You can register your RKSerialization implementation

[RKMIMETypeSerialization registerClass:[NKJsonSerialization class]
                               forMIMEType:RKMIMETypeJSON];

And in your serialization implementation, you can check if response is erroneous response and create a NSError object then send it back to Restkit.

@implementation NKJsonSerialization
+ (id)objectFromData:(NSData *)data error:(NSError **)error
{
    NSError* serializingError = nil;
    NSMutableDictionary*  jsonObject = [NSJSONSerialization
                                        JSONObjectWithData:data
                                        options:NSJSONReadingMutableContainers
                                        error:&serializingError];

    // Process if there is no error
    if (!serializingError)
    {
        NSString* resCodeStr = [jsonObject objectForKey:@"resultCode"];
        
        if ([resCodeStr intValue] != 0) {
        {
            // Create your NSError for your domain, contain information about response err
            serializingError == <#new created error#>
            jsonObject = nil;
        }else{
            [jsonObject removeObjectForKey: @"resultCode"];
            [jsonObject removeObjectForKey: @"resultDescription"];
            serializingError = nil;
        }
    }
    
    *error = serializingError;
    return jsonObject;
}

If your response contains erroneous code, In your request callback, RestKit will return a NSError object with underlying error is the error you just created during serialization.

The beauty of this solution is that you dont have to care about mapping response status. The erroneous response will be (should be) handled as an NSError.

And if the json object contains data object at the top level (key path @"") you still can use mapping to obtain it without key conflict as it would happen in solution #1.

OTHER TIPS

I'm not sure what you're asking exactly.. RKEntityMapping is used to map to Core Data entities, and RKObjectMapping is used to just map to regular object representations. So maybe the question is, are you using Core Data or not?

You do not need to mix them in this case. When you create your response descriptor you set the key path to resultContent and just use an entity mapping.

It is possible to mix the mapping types in some ways but this generally needs to be considered on a case-by-case basis. Often you would use multiple response descriptors to keep the mappings separate and then combine the results as post processing.

You needs to define two separate descriptors for RKObjectMapping & RKEntityMapping objects, in your case for StatusMapping & RegionMapping then add them to ObjectManager, it works like charm (I am giving you sample code and classes to achieve this):

Define ResponseStatus class like this :

//...

ResponseStatus.h

//...

@interface ResponseStatus : NSObject

@property (nonatomic) BOOL resultCode;
@property (nonatomic, strong) NSString *resultDescription;

+ (RKObjectMapping *)rkObjectMappingForResponse:(BOOL)includeAll;
+ (RKObjectMapping *)rkObjectMappingForRequest:(BOOL)includeAll;
+ (NSDictionary *)rkAttributeMappingsDictionary:(BOOL)request includeAll:(BOOL)includeAll;

@end

//...

ResponseStatus.m

//...

@implementation ResponseStatus

    + (RKObjectMapping *)rkObjectMappingForResponse:(BOOL)includeAll {
        RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[ResponseStatus class]];
        [mapping addAttributeMappingsFromDictionary:[self rkAttributeMappingsDictionary:NO includeAll:includeAll]];

        if (includeAll) {
        }

        return mapping;
    }

    + (RKObjectMapping *)rkObjectMappingForRequest:(BOOL)includeAll {
        RKObjectMapping *mapping = [RKObjectMapping requestMapping];
        [mapping addAttributeMappingsFromDictionary:[self rkAttributeMappingsDictionary:YES includeAll:includeAll]];

        if (includeAll) {
        }

        return mapping;
    }

    + (NSDictionary *)rkAttributeMappingsDictionary:(BOOL)request includeAll:(BOOL)includeAll {
        NSMutableDictionary *dic = [NSMutableDictionary dictionary];

        if (includeAll) {
            [dic addEntriesFromDictionary:@{
                 @"resultCode": @"resultCode",
                 @"resultDescription": @"resultDescription",
             }];
        }

        return dic;
    }

    @end

Define a descriptor Mapping for your ResponseStatus (results)

NSIndexSet *statusCodes = RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful); // Anything in 2xx

RKResponseDescriptor *statusResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:[ResponseStatus rkObjectMappingForResponse:YES] method:RKRequestMethodAny pathPattern:nil keyPath:@"" statusCodes:statusCodes];

Define a descriptor Mapping for your RKEntityMapping

RKResponseDescriptor *gameResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:[Game rkEntityMappingForResponse:YES] method:RKRequestMethodAny pathPattern:nil keyPath:@"games" statusCodes:statusCodes];

Add response descriptors to objectManager

[objectManager addResponseDescriptorsFromArray:@[gameResponseDescriptor, statusResponseDescriptor]];

This is how to handle Mapping Results

RKObjectRequestOperation *operation = [objectManager managedObjectRequestOperationWithRequest:requestObject managedObjectContext:managedObjectContext success: ^(RKObjectRequestOperation *operation, RKMappingResult *result) {

    if ([RKUtils isResponseStatusError:[result array]]) 
        { 
         //..       
        }

        } failure: ^(RKObjectRequestOperation *operation, NSError *error) 
        {
        NSLog(@"Failed with error: %@", [error localizedDescription]);  
        }];


+ (BOOL)isResponseStatusError:(NSArray *)itemsList {
    if ([itemsList count] != 1) {
        return NO;
    }

    id object = itemsList[0];

    if ([object isKindOfClass:[ResponseStatus class]]) {
        ResponseStatus *responseStatus = object;
        if (!responseStatus.resultCode) {
            NSLog(@"Error : %@", responseStatus.message);
            return YES;
        }
    }

    return NO;
}

and make a REST call, Hope thats helping.

OK, after a few tests, I realised RestKit saves the RKEntityMapping in core data but not the RKObjectMapping. It works perfectly. I love RestKit :)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top