UPDATE: SOLUTION DOCUMENTED IN ANSWERS BELOW
I'm having trouble with RestKit mapping using the new #root #parent accessors in 0.20.3. I'm not sure if it's a bug or a misunderstanding of how to use the framework properly.
PROBLEM
The new concept of #root and #parent doesn't seem to be working for me.
EDIT: Removed a bunch of discussion about what I thought the problem was. I was way wrong so there is no need in digesting it. If the problem statement above applies to you... then this SO post might help get you going.
BACKGROUND
Example source XML can be downloaded here
The basic structure of the XML is as follows:
<locations>
<location lat="38.8561" lon="-94.6654" timezone="UTC" city="Overland Park" region="KS" country="US" zipcode="66223" offset="0" local_offset_hours="-5">
<sfc_ob>
<attribute1></attribute1>
</sfc_ob>
<daily_summaries>
<daily_summary>
<attribute2> </attribute2>
</daily_summary>
</daily_summaries>
<hourly_summaries>
<hourly_summary>
<attribute3></attribute3>
</hourly_summary>
</hourly_summaries>
</location>
</locations>
My Core Data Entities are as follows:
RESTKIT RELATED CODE
- (GLWeatherManager *)init {
self = [super init];
// setup logging
RKLogConfigureByName("RestKit/Network*", RKLogLevelTrace);
RKLogConfigureByName("RestKit/ObjectMapping", RKLogLevelTrace);
self.httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:#"http://weather.wdtinc.com"]];
[self.httpClient setDefaultHeader:#"Accept" value:RKMIMETypeXML];
[RKMIMETypeSerialization registerClass:[RKXMLReaderSerialization class] forMIMEType:#"application/xml"];
self.restKitManager = [[RKObjectManager alloc] initWithHTTPClient:self.httpClient];
self.restKitManager.managedObjectStore = [[RKManagedObjectStore alloc] initWithPersistentStoreCoordinator:[NSPersistentStoreCoordinator MR_defaultStoreCoordinator]];
[self.restKitManager.managedObjectStore createManagedObjectContexts];
// Locations
RKEntityMapping *locationMapping = [self buildMapForLocations];
RKEntityMapping *currentConditionsMapping = [self buildMapForCurrentConditions];
RKEntityMapping *dailySummariesMapping = [self buildMapForDailySummaries];
RKEntityMapping *hourlyForecastsMapping = [self buildMapForHourlyForecasts];
[locationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"daily_summaries" toKeyPath:#"dailySummaries" withMapping:dailySummariesMapping]];
[locationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"hourly_summaries" toKeyPath:#"hourlyForecasts" withMapping:hourlyForecastsMapping]];
[locationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"sfc_ob" toKeyPath:#"currentConditions" withMapping:currentConditionsMapping]];
[dailySummariesMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:nil toKeyPath:#"location" withMapping:locationMapping]];
[hourlyForecastsMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:nil toKeyPath:#"location" withMapping:locationMapping]];
RKResponseDescriptor *descriptor = [RKResponseDescriptor responseDescriptorWithMapping:locationMapping pathPattern:#"/feeds/demofeeds20131031/mega.php" keyPath:#"locations.location" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
// add mapping description to objectmanager
[self.restKitManager addResponseDescriptor:descriptor];
RKResponseDescriptor *descriptor2 = [RKResponseDescriptor responseDescriptorWithMapping:currentConditionsMapping pathPattern:#"/feeds/demofeeds20131031/mega.php" keyPath:#"locations.location.sfc_ob" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[self.restKitManager addResponseDescriptor:descriptor2];
RKResponseDescriptor *descriptor3 = [RKResponseDescriptor responseDescriptorWithMapping:dailySummariesMapping pathPattern:#"/feeds/demofeeds20131031/mega.php" keyPath:#"locations.location.daily_summaries.daily_summary" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[self.restKitManager addResponseDescriptor:descriptor3];
RKResponseDescriptor *descriptor4 = [RKResponseDescriptor responseDescriptorWithMapping:hourlyForecastsMapping pathPattern:#"/feeds/demofeeds20131031/mega.php" keyPath:#"locations.location.hourly_summaries.hourly_summary"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[self.restKitManager addResponseDescriptor:descriptor4];
// start the location manager to get the current location
self.locationManager = [[CLLocationManager alloc] init];
[self.locationManager setDelegate:self];
[self.locationManager startUpdatingLocation];
self.locations = [NSMutableArray arrayWithArray:[Locations findAll]];
[self getMegaFeed];
return self;
}
- (RKEntityMapping *)buildMapForLocations {
RKEntityMapping *locationMapping = [RKEntityMapping mappingForEntityForName:#"Locations" inManagedObjectStore:self.restKitManager.managedObjectStore];
[locationMapping addAttributeMappingsFromDictionary:#{
#"lat" : #"latitude",
#"lon" : #"longitude",
#"city" : #"city",
#"region" : #"region",
#"country" : #"country",
#"zipcode" : #"zipcode",
}];
locationMapping.identificationAttributes = [NSArray arrayWithObject:#"zipcode"];
return locationMapping;
}
- (RKEntityMapping *)buildMapForCurrentConditions {
// Current Conditions
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:#"CurrentConditions" inManagedObjectStore:self.restKitManager.managedObjectStore];
[mapping addAttributeMappingsFromDictionary:#{
//#"stn" : #"stn",
//#"location" : #"location",
//#"stn_lat" : #"stnLatitude",
//#"stn_lon" : #"stnLongitude",
#"ob_time.text" : #"observationTime",
#"day_of_week.text" : #"dayOfWeek",
#"temp_C.text" : #"temperatureMetric",
#"temp_F.text" : #"temperatureImperial",
#"dewp_C.text" : #"dewPointMetric",
#"dewp_F.text" : #"dewPointImperial",
#"rh_pct.text" : #"relativeHumidity",
#"wnd_dir.text" : #"windDirection",
#"wnd_spd_mph.text" : #"windSpeedImperial",
#"wnd_spd_kph.text" : #"windSpeedMetric",
#"press_in.text" : #"pressureImperial",
#"press_mb.text" : #"pressureMetric",
#"wx.text" : #"conditionSummary",
#"wx_code.text" : #"conditionCode",
#"cld_cover.text" : #"cloudCover",
#"visibility_ft.text" : #"visibilityImperial",
#"visibility_m.text" : #"visibilityMetric",
#"apparent_temp_F.text" : #"feelsLikeTemperatureImperial",
#"apparent_temp_C.text" : #"feelsLikeTemperatureMetric",
#"moon_phase.text" : #"moonPhase",
#"sunrise_utc.text" : #"sunrise",
#"sunset_utc.text" : #"sunset"
}];
[mapping setIdentificationAttributes:[NSArray arrayWithObjects:#"observationTime", nil]];
return mapping;
}
- (RKEntityMapping *)buildMapForDailySummaries {
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:#"DailySummaries" inManagedObjectStore:self.restKitManager.managedObjectStore];
[mapping addAttributeMappingsFromDictionary:#{
#"summary_date.text" : #"date",
#"day_of_week.text" : #"dayOfWeek",
#"max_temp_F.text" : #"tempMaxImperial",
#"max_temp_C.text" : #"tempMaxMetric",
#"min_temp_F.text" : #"tempMinImperial",
#"min_temp_C.text" : #"tempMinMetric",
#"wnd_spd_mph.text" : #"windSpeedImperial",
#"wnd_spd_kph.text" : #"windSpeedMetric",
#"min_wnd_spd_mph.text" : #"windSpeedMinImperial",
#"min_wnd_spd_kph.text" : #"windSpeedMinMetric",
#"max_wnd_spd_mph.text" : #"windSpeedMaxImperial",
#"max_wnd_spd_kph.text" : #"windSpeedMaxMetric",
#"wnd_gust_mph.text" : #"windGustImperial",
#"wnd_gust_kph.text" : #"windGustMetric",
#"wnd_dir.text" : #"windDirection",
#"pop.text" : #"probabilityOfPrecipitation",
#"wx.text" : #"conditionSummary",
#"wx_code.text" : #"conditionCode",
#"text_description.text" : #"textDescription",
#"sunrise_utc.text" : #"sunrise",
#"sunset_utc.text" : #"sunset",
#"#root.locations.location.zipcode" : #"locationZipcode"
}];
mapping.identificationAttributes = [NSArray arrayWithObjects:#"date", #"locationZipcode", nil];
[mapping addConnectionForRelationship:#"location" connectedBy:#{#"locationZipcode": #"zipcode"}];
return mapping;
}
- (RKEntityMapping *)buildMapForHourlyForecasts {
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:#"HourlyForecasts" inManagedObjectStore:self.restKitManager.managedObjectStore];
[mapping addAttributeMappingsFromDictionary:#{
#"day_of_week_utc.text" : #"dayOfWeek",
#"time_utc.text" : #"forecastTime",
#"temp_C.text" : #"temperatureMetric",
#"temp_F.text" : #"temperatureImperial",
#"dewp_C.text" : #"dewPointMetric",
#"dewp_F.text" : #"dewPointImperial",
#"app_temp_C.text" : #"feelsLikeTemperatureMetric",
#"app_temp_F.text" : #"feelsLikeTemperatureImperial",
#"rh_pct.text" : #"relativeHumidity",
#"wx.text" : #"conditionSummary",
#"wx_code.text" : #"conditionCode",
#"day_night.text" : #"dayNight",
#"pop.text" : #"probabilityOfPrecipitation",
#"sky_cov_pct.text" : #"skyCoverPercent",
#"wnd_dir.text" : #"windDirection",
#"wnd_dir_degs.text" : #"windDirectionDegrees",
#"wnd_spd_mph.text" : #"windSpeedImperial",
#"wnd_spd_kph.text" : #"windSpeedMetric",
#"#root.locations.location.zipcode" : #"locationZipcode"
}];
mapping.identificationAttributes = [NSArray arrayWithObjects:#"forecastTime", #"locationZipcode", nil];
[mapping addConnectionForRelationship:#"location" connectedBy:#{#"locationZipcode": #"zipcode"}];
return mapping;
}
- (void)getMegaFeed {
for (Locations *location in self.locations) {
NSString *path = [NSString stringWithFormat:#"/feeds/demofeeds20131031/mega.php?ZIP=%#&UNITS=all",location.zipcode];
// fetch data
[self.restKitManager getObjectsAtPath:path
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSArray *mappedObjects = [mappingResult array];
NSMutableArray *validObjectIDs = [[NSMutableArray alloc] initWithCapacity:[mappedObjects count]];
for (NSManagedObject *object in mappedObjects) {
NSManagedObjectID *moID = [object objectID];
[validObjectIDs addObject:moID];
}
[self notifyObservers:#selector(megaFeedDidFinish:location:) withObject:validObjectIDs withObject:location];
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
[REUtility showDefaultAlertWithError:error];
[RELog error:#"%s Hit error:%#", __func__, error];
}];
}
}
I think the problem is the key paths you're using in the response descriptors for the mappings where you try to use #root. When you specify a keypath on the response descriptor you are effectively changing the #root object because you are diving into the content to a specified depth and that depth becomes the root for that mapping. If you debug during the mapping process and look at the supplied metadata you should be able to see / verify this.
I'm not clear on why you have so many different response descriptors. It would seem more logical to have one response descriptor for locations whose mapping defines all of the relationships between all of the different parts (and their mappings). Working in this way you would have much simpler code and you would also have access to #root / #parent.
Fellow Stacker
This is the solution that has materialized as a result of Wain's suggestion above. I am posting here not to collect points but to keep the long contents of both the problem and the final solution separate. Please award any upvotes to #Wain, I am simply trying to post a concise problem-to-solution example regarding this issue.
I find RestKit to be one of the most awesome (for several reasons) 3rd party framework I have ever seen. There is a lot of community involvement; however, documentation and examples are sparse for "out of bounds" or uncommon scenarios and usages. I suspect most people use RestKit with JSON and complex real world XML examples are sparse. I hope this helps others who have the same type of issue.
SOLUTION
Wain was correct in the sense that I was building multiple response descriptors targeting each point in the hierarchy of the XML (available in the problem above) I was after. This was preventing RestKit from understanding that there was ever a (root) or (parent) to access.
Because I needed to map these to multiple entities in Core Data I should have had one large response descriptor at the highest tag level of the XML.
That meant if I map the single (in my xml) high level locations tag and I have properly configured the RestKit relationships it will do the rest for you.
IMPLEMENTATION
The first thing I realized was that I didn't care about the high level Locations tag in the XML and I didn't need to map it to a Core Data Entity so I built an RKObjectMapping instead of an RKEntityMapping to handle the processing of the high level tag in the XML.
The complete solution is below but I'll walk through the changes.
Note the new RKObjectMapping *locationsMapping = [self buildMapForLocations]; and the corresponding method.
I then needed to tell the new locationsMapping about the Location entity to be mapped to Core Data
[locationsMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"location" toKeyPath:#"location" withMapping:locationMapping]];
The next big changes, thanks to Wain, were the keypath changes to the relationships as follows:
[locationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"daily_summaries.daily_summary" toKeyPath:#"dailySummaries" withMapping:dailySummariesMapping]];
[locationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"hourly_summaries.hourly_summary" toKeyPath:#"hourlyForecasts" withMapping:hourlyForecastsMapping]];
[locationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"sfc_ob" toKeyPath:#"currentConditions" withMapping:currentConditionsMapping]];
Here we tell the locationMapping (that maps to Core Data) where we are mapping from in the source XML to where we are going in Core Data. Since we are on the Location mapping, in the source XML the keyPath to my dailySummaries (entity) is "daily_summaries.daily_summary". Likewise, the correct keyPath to hourlyForecasts was "hourly_summaries.hourly_summary" and from location in the XML "sfc_ob" maps to currentConditions (entity).
Finally all of my response descriptors went away except one easy, simple response descriptor.
RKResponseDescriptor *descriptor = [RKResponseDescriptor responseDescriptorWithMapping:locationsMapping
method:RKRequestMethodGET
pathPattern:#"/feeds/demofeeds20131031/mega.php"
keyPath:#"locations"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[self.restKitManager addResponseDescriptor:descriptor];
Everything in the success block of the call to RestKit in the problem statement (above) was eliminated as everything including relationships had all been mapped properly. Note that all of the problem statement code for the mappings were still valid except for a minor tweak to the #root #parent aspect of the solution.
When mapping the daily summaries and hourly forecasts I still needed to get the zipcode from the location to be populated into the Core Data entity so that RestKit can use this as a key on future calls to check the database to see if a record with this key already exists (to update instead of create).
I tweaked the # mappings to grab the zipcode from location. Note the desired use of the #parent.#parent traversal of the hierarchy as follows:
- (RKEntityMapping *)buildMapForDailySummaries {
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:#"DailySummaries" inManagedObjectStore:self.restKitManager.managedObjectStore];
[mapping addAttributeMappingsFromDictionary:#{
#"summary_date.text" : #"date",
#"day_of_week.text" : #"dayOfWeek",
#"max_temp_F.text" : #"tempMaxImperial",
#"max_temp_C.text" : #"tempMaxMetric",
#"min_temp_F.text" : #"tempMinImperial",
#"min_temp_C.text" : #"tempMinMetric",
#"wnd_spd_mph.text" : #"windSpeedImperial",
#"wnd_spd_kph.text" : #"windSpeedMetric",
#"min_wnd_spd_mph.text" : #"windSpeedMinImperial",
#"min_wnd_spd_kph.text" : #"windSpeedMinMetric",
#"max_wnd_spd_mph.text" : #"windSpeedMaxImperial",
#"max_wnd_spd_kph.text" : #"windSpeedMaxMetric",
#"wnd_gust_mph.text" : #"windGustImperial",
#"wnd_gust_kph.text" : #"windGustMetric",
#"wnd_dir.text" : #"windDirection",
#"pop.text" : #"probabilityOfPrecipitation",
#"wx.text" : #"conditionSummary",
#"wx_code.text" : #"conditionCode",
#"text_description.text" : #"textDescription",
#"sunrise_utc.text" : #"sunrise",
#"sunset_utc.text" : #"sunset",
#"#parent.#parent.zipcode" : #"locationZipcode"
}];
mapping.identificationAttributes = [NSArray arrayWithObjects:#"date", #"locationZipcode", nil];
[mapping addConnectionForRelationship:#"location" connectedBy:#{#"locationZipcode": #"zipcode"}];
return mapping;
}
- (RKEntityMapping *)buildMapForHourlyForecasts {
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:#"HourlyForecasts" inManagedObjectStore:self.restKitManager.managedObjectStore];
[mapping addAttributeMappingsFromDictionary:#{
#"day_of_week_utc.text" : #"dayOfWeek",
#"time_utc.text" : #"forecastTime",
#"temp_C.text" : #"temperatureMetric",
#"temp_F.text" : #"temperatureImperial",
#"dewp_C.text" : #"dewPointMetric",
#"dewp_F.text" : #"dewPointImperial",
#"app_temp_C.text" : #"feelsLikeTemperatureMetric",
#"app_temp_F.text" : #"feelsLikeTemperatureImperial",
#"rh_pct.text" : #"relativeHumidity",
#"wx.text" : #"conditionSummary",
#"wx_code.text" : #"conditionCode",
#"day_night.text" : #"dayNight",
#"pop.text" : #"probabilityOfPrecipitation",
#"sky_cov_pct.text" : #"skyCoverPercent",
#"wnd_dir.text" : #"windDirection",
#"wnd_dir_degs.text" : #"windDirectionDegrees",
#"wnd_spd_mph.text" : #"windSpeedImperial",
#"wnd_spd_kph.text" : #"windSpeedMetric",
#"#parent.#parent.zipcode" : #"locationZipcode"
}];
mapping.identificationAttributes = [NSArray arrayWithObjects:#"forecastTime", #"locationZipcode", nil];
[mapping addConnectionForRelationship:#"location" connectedBy:#{#"locationZipcode": #"zipcode"}];
return mapping;
}
- (RKObjectMapping *)buildMapForLocations {
RKObjectMapping *locationsMapping = [[RKObjectMapping alloc] initWithClass:[WDTMegaFeedResponse class]];
[locationsMapping addAttributeMappingsFromDictionary:#{
#"language.text" : #"language",
}];
return locationsMapping;
}
The changes to the mappings here and the changes below is the complete solution for this use case.
- (GLWeatherManager *)init {
self = [super init];
self.cloud = [[Kumulos alloc] init];
[self.cloud setDelegate:self];
// Check to see if the states have been loaded
NSArray *states = [States findAllSortedBy:#"code" ascending:YES];
if ([states count] == 0) {
[self.cloud getStates];
}
// setup logging
RKLogConfigureByName("RestKit/Network*", RKLogLevelTrace);
RKLogConfigureByName("RestKit/ObjectMapping", RKLogLevelTrace);
// initialize the network layer and ensure we are using the same store magical record is using
self.httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:#"http://weather.wdtinc.com"]];
[self.httpClient setDefaultHeader:#"Accept" value:RKMIMETypeXML];
[RKMIMETypeSerialization registerClass:[RKXMLReaderSerialization class] forMIMEType:#"application/xml"];
self.restKitManager = [[RKObjectManager alloc] initWithHTTPClient:self.httpClient];
self.restKitManager.managedObjectStore = [[RKManagedObjectStore alloc] initWithPersistentStoreCoordinator:[NSPersistentStoreCoordinator MR_defaultStoreCoordinator]];
[self.restKitManager.managedObjectStore createManagedObjectContexts];
// Locations
RKObjectMapping *locationsMapping = [self buildMapForLocations];
RKEntityMapping *hourlyForecastsMapping = [self buildMapForHourlyForecasts];
RKEntityMapping *dailySummariesMapping = [self buildMapForDailySummaries];
RKEntityMapping *currentConditionsMapping = [self buildMapForCurrentConditions];
RKEntityMapping *locationMapping = [self buildMapForLocation];
[locationsMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"location" toKeyPath:#"location" withMapping:locationMapping]];
[locationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"daily_summaries.daily_summary" toKeyPath:#"dailySummaries" withMapping:dailySummariesMapping]];
[locationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"hourly_summaries.hourly_summary" toKeyPath:#"hourlyForecasts" withMapping:hourlyForecastsMapping]];
[locationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"sfc_ob" toKeyPath:#"currentConditions" withMapping:currentConditionsMapping]];
RKResponseDescriptor *descriptor = [RKResponseDescriptor responseDescriptorWithMapping:locationsMapping
method:RKRequestMethodGET
pathPattern:#"/feeds/demofeeds20131031/mega.php"
keyPath:#"locations"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[self.restKitManager addResponseDescriptor:descriptor];
}
Also some might find the header file of the RKObjectMapping object used to capture the high level XML tag useful.
#import <Foundation/Foundation.h>
#import "Locations.h"
#interface WDTMegaFeedResponse : NSObject
#property (nonatomic, strong) NSString *language;
#property (nonatomic, strong) Locations *location;
#end
Related
I am creating a response descriptor for json to core data mapping in RestkitManager. The parent object is "level" and it has an array of "sublevel" objects.
RKDynamicMapping *levelMapping = [Level map];
RKResponseDescriptor* levelRd = [RKResponseDescriptor responseDescriptorWithMapping:levelMapping method:RKRequestMethodGET pathPattern:#"entity/:entityId" keyPath:#"summary.levels" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[self.objectManager addResponseDescriptor:levelRd];
In Level class
+ (RKEntityMapping *)mapping {
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:NSStringFromClass([self class]) inManagedObjectStore:[RKManagedObjectStore defaultStore]];
[mapping addAttributeMappingsFromDictionary:#{
#"id" : #"id",
#"name" : #"name",
#"state" : #"state"
}];
[mapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"sublevel" toKeyPath:#"sublevelList" withMapping:[Sublevel map]]];
return mapping;
}
In Sublevel
+ (RKEntityMapping *)map {
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:NSStringFromClass([self class]) inManagedObjectStore:[RKManagedObjectStore defaultStore]];
[mapping addAttributeMappingsFromDictionary:#{
#"id" : #"id",
#"staticNode.obj.name" : #"name"
}];
return mapping;
}
When I try to fetch sublevel on object level, I get it in random order. Sometimes 2nd sublevel get printed first. Is there any way to maintain the order?
As I understand, when mapping is done, I don't have any control over what is getting persisted in the database. Hence, I am not able to assign any order number myself. Apart from that, I have explored metadata.routing.parameters but for this I need to pass parameters in the API call itself - which is not desirable.
Any pointers on how to maintain the order would be helpful.
I am reviving an old project that originally used RestKit 0.10, and now am using RestKit 0.24. The old version still works, but unfortunately RestKit 0.10 is not 64-bit compatible and hence does not submit to the AppStore (and it is certainly time to update anyway).
I cannot get an object to post correctly. In RestKit 0.10, properties without values were not sent to the server, whereas it seems in RestKit 0.20 they are. I have tried explicitly setting assignsDefaultValueForMissingAttributes to NO, but it doesn't seem to make a difference.
The server expects the following format:
{"response": {"assessment_id":"1","time_taken":"60"},
"answer": [
{"question_number": 1, "answer_value": 3},
{"question_number": 2, "answer_value": 2},
{"question_number": 3, "answer_value": 1},
]
}
I have set up an object CompletedAssessment which contains a Response object and an array of Answer objects. (Note that when these objects are received from the server, many more properties need to be received than need to be sent).
#interface CompletedAssessment : NSObject {
Response *response;
NSArray *answers;
}
#interface Answer : NSObject {
NSNumber *identifier;
NSNumber *responseId;
NSNumber *questionNumber;
NSString *answerHistory;
NSString *answerValue;
NSString *answerText;
NSNumber *timeTaken;
}
#interface Response : NSObject {
NSNumber *identifier;
NSNumber *assessmentId;
NSNumber *timeTaken;
NSNumber *clientId;
NSString *assessmentShortName;
NSString *score;
NSString *interpretation;
NSString *dateCreated;
NSString *localTime;
}
I set the mapping up as follows:
RKObjectMapping *answerMapping = [RKObjectMapping mappingForClass:[Answer class]];
answerMapping.assignsDefaultValueForMissingAttributes = NO;
[answerMapping addAttributeMappingsFromDictionary:#{
#"id": #"identifier",
#"response_id": #"responseId",
#"question_number": #"questionNumber",
#"answer_history": #"answerHistory",
#"answer_value": #"answerValue",
#"answer_text": #"answerText",
#"time_taken": #"timeTaken"
}];
RKObjectMapping *responseMapping = [RKObjectMapping mappingForClass:[Response class]];
responseMapping.assignsDefaultValueForMissingAttributes = NO;
[responseMapping addAttributeMappingsFromDictionary:#{
#"id": #"identifier",
#"client_id": #"clientId",
#"assessment_id": #"assessmentId",
#"time_taken": #"timeTaken",
#"score": #"score",
#"assessment_short_name": #"assessmentShortName",
#"interpretation": #"interpretation",
#"created": #"dateCreated",
#"local_time": #"localTime"
}];
RKObjectMapping *completedAssessmentMapping = [RKObjectMapping mappingForClass:[CompletedAssessment class]];
completedAssessmentMapping.assignsDefaultValueForMissingAttributes = NO;
[completedAssessmentMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"response" toKeyPath:#"response" withMapping:responseMapping]];
[completedAssessmentMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"answer" toKeyPath:#"answers" withMapping:answerMapping]];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:completedAssessmentMapping method:RKRequestMethodGET pathPattern:nil keyPath:#"data.completedAssessment" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[[RKObjectManager sharedManager] addResponseDescriptor:responseDescriptor];
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:[completedAssessmentMapping inverseMapping] objectClass:[CompletedAssessment class] rootKeyPath:nil method:RKRequestMethodPOST];
[[RKObjectManager sharedManager] addRequestDescriptor:requestDescriptor];
[objectManager.router.routeSet addRoute:[RKRoute
routeWithClass:[CompletedAssessment class]
pathPattern:#"clients/:response.clientId/responses"
method:RKRequestMethodPOST]] ;
Logging reveals the end JSON appears in this format:
{"response":
{"interpretation":null,"id":null,"score":null,"client_id":15,"local_time":"2015-8-6 13:8:34","time_taken":5,"assessment_short_name":null,"assessment_id":8,"created":null},
"answer":[
{"answer_value":"0","id":null,"answer_text":null,"answer_history":null,"time_taken":null,"response_id":null,"question_number":1},
{"answer_value":"1","id":null,"answer_text":null,"answer_history":null,"time_taken":null,"response_id":null,"question_number":2}
]}
And RestKit logging confirms the null mapping:
restkit.object_mapping:RKMappingOperation.m:873 Mapped relationship object from keyPath 'response' to 'response'. Value: {
"assessment_id" = 8;
"assessment_short_name" = "<null>";
"client_id" = 15;
created = "<null>";
id = "<null>";
interpretation = "<null>";
"local_time" = "2015-8-6 13:8:34";
score = "<null>";
"time_taken" = 5;
}
restkit.object_mapping:RKMappingOperation.m:715 Mapped attribute value from keyPath 'identifier' to 'id'. Value: (null)
...
Please help!
You are creating a new mapping calling [selfCompletedAssessmentMapping inverseMapping] in this line:
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:[selfCompletedAssessmentMapping inverseMapping] objectClass:[CompletedAssessment class] rootKeyPath:nil method:RKRequestMethodPOST];
Save it to a variable and assign assignsDefaultValueForMissingAttributes to NO before creating the descriptor:
RKObjectMapping *requestMapping = [selfCompletedAssessmentMapping inverseMapping];
requestMapping.assignsDefaultValueForMissingAttributes = NO;
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:requestMapping objectClass:[CompletedAssessment class] rootKeyPath:nil method:RKRequestMethodPOST];
I have the following JSON:
"Menus":{
"Id" : "3",
"Name" : "Cheese Burger",
"Items": []
}
I map the response to Core Data like so:
+ (RKEntityMapping *)menuMapping {
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:#"Menu" inManagedObjectStore:[[CoreDataManager sharedInstance] objectStore]];
mapping.assignsNilForMissingRelationships = YES;
mapping.assignsDefaultValueForMissingAttributes = YES;
[mapping addAttributeMappingsFromDictionary:#{
#"Id": #"remoteID",
#"Name": #"name",
}];
mapping.identificationAttributes = #[ #"remoteID" ];
[mapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"Items"
toKeyPath:#"products"
withMapping:[MappingProvider productMapping]]];
return mapping;
}
+ (RKEntityMapping *)productMapping {
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:#"Product" inManagedObjectStore:[[CoreDataManager sharedInstance] objectStore]];
mapping.assignsNilForMissingRelationships = YES;
[mapping addAttributeMappingsFromDictionary:#{
#"Id": #"remoteID",
#"Name": #"name",
#"Description" : #"info",
#"Price": #"price"
}];
mapping.identificationAttributes = #[ #"remoteID" ];
return mapping;
}
How would I manage to validate if the Items array is empty or not, and create the Menu object in Core Data based on if the Items (the products) contains values? I have tried using the assignsNilForMissingRelationships but it does not seem to work in this case.
Use KVC validation to analyse the incoming value and (modify or) reject it.
I'm having trouble with RestKit mapping relationships (0.20.3). I've looked at other posts but haven't found a solution yet. I think I'm close but still missing something.
PROBLEM
Entity attributes are mapping just fine into Core Data; however, if the entity has a relationship it isn't getting mapped. I have a workaround in place that I need to get rid of as it is now reaching its limits.
For the workaround, after a successful mapping I have passed the managed object IDs to a foreground thread to hydrate a valid managed object. I then loop through the objects and establish the relationships then "resaving" to Core Data.
The problem with the workaround is that the mapped relationship is part of the unique key for the identificationAttributes parameter. Because the mapping isn't working properly in the background the existing record is not being properly identified and thus duplicate entries occur on future API calls.
BACKGROUND
Example source XML can be downloaded here
The basic structure of the XML is as follows:
<locations>
<location lat="38.8561" lon="-94.6654" timezone="UTC" city="Overland Park" region="KS" country="US" zipcode="66223" offset="0" local_offset_hours="-5">
<sfc_ob>
<attribute1></attribute1>
</sfc_ob>
<daily_summaries>
<daily_summary>
<attribute2> </attribute2>
<daily_summary>
</daily_summaries>
<hourly_summaries>
<hourly_summary>
<attribute3></attribute3>
</hourly_summary>
</hourly_summaries>
</location>
</locations>
My Core Data Entities are as follows:
RESTKIT RELATED CODE
- (GLWeatherManager *)init {
self = [super init];
// setup logging
RKLogConfigureByName("RestKit/Network*", RKLogLevelTrace);
RKLogConfigureByName("RestKit/ObjectMapping", RKLogLevelTrace);
self.httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:#"http://weather.wdtinc.com"]];
[self.httpClient setDefaultHeader:#"Accept" value:RKMIMETypeXML];
[RKMIMETypeSerialization registerClass:[RKXMLReaderSerialization class] forMIMEType:#"application/xml"];
self.restKitManager = [[RKObjectManager alloc] initWithHTTPClient:self.httpClient];
self.restKitManager.managedObjectStore = [[RKManagedObjectStore alloc] initWithPersistentStoreCoordinator:[NSPersistentStoreCoordinator MR_defaultStoreCoordinator]];
[self.restKitManager.managedObjectStore createManagedObjectContexts];
// Locations
RKEntityMapping *locationMapping = [self buildMapForLocations];
RKEntityMapping *currentConditionsMapping = [self buildMapForCurrentConditions];
RKEntityMapping *dailySummariesMapping = [self buildMapForDailySummaries];
RKEntityMapping *hourlyForecastsMapping = [self buildMapForHourlyForecasts];
[locationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"locations.location.daily_summaries" toKeyPath:#"dailySummaries" withMapping:dailySummariesMapping]];
[locationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"locations.location.hourly_summaries" toKeyPath:#"hourlyForecasts" withMapping:hourlyForecastsMapping]];
[locationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"locations.location.sfc_ob" toKeyPath:#"currentConditions" withMapping:currentConditionsMapping]];
[currentConditionsMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"locations.location" toKeyPath:#"location" withMapping:locationMapping]];
[dailySummariesMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"locations.location" toKeyPath:#"location" withMapping:locationMapping]];
[hourlyForecastsMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"locations.location" toKeyPath:#"location" withMapping:locationMapping]];
RKResponseDescriptor *descriptor = [RKResponseDescriptor responseDescriptorWithMapping:locationMapping pathPattern:#"/feeds/demofeeds20131031/mega.php" keyPath:#"locations.location" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
// add mapping description to objectmanager
[self.restKitManager addResponseDescriptor:descriptor];
RKResponseDescriptor *descriptor2 = [RKResponseDescriptor responseDescriptorWithMapping:currentConditionsMapping pathPattern:#"/feeds/demofeeds20131031/mega.php" keyPath:#"locations.location.sfc_ob" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[self.restKitManager addResponseDescriptor:descriptor2];
RKResponseDescriptor *descriptor3 = [RKResponseDescriptor responseDescriptorWithMapping:dailySummariesMapping pathPattern:#"/feeds/demofeeds20131031/mega.php" keyPath:#"locations.location.daily_summaries.daily_summary" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[self.restKitManager addResponseDescriptor:descriptor3];
RKResponseDescriptor *descriptor4 = [RKResponseDescriptor responseDescriptorWithMapping:hourlyForecastsMapping pathPattern:#"/feeds/demofeeds20131031/mega.php" keyPath:#"locations.location.hourly_summaries.hourly_summary"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[self.restKitManager addResponseDescriptor:descriptor4];
// start the location manager to get the current location
self.locationManager = [[CLLocationManager alloc] init];
[self.locationManager setDelegate:self];
[self.locationManager startUpdatingLocation];
self.locations = [NSMutableArray arrayWithArray:[Locations findAll]];
[self getMegaFeed];
return self;
}
- (RKEntityMapping *)buildMapForLocations {
RKEntityMapping *locationMapping = [RKEntityMapping mappingForEntityForName:#"Locations" inManagedObjectStore:self.restKitManager.managedObjectStore];
[locationMapping addAttributeMappingsFromDictionary:#{
#"lat" : #"latitude",
#"lon" : #"longitude",
#"city" : #"city",
#"region" : #"region",
#"country" : #"country",
#"zipcode" : #"zipcode",
}];
locationMapping.identificationAttributes = [NSArray arrayWithObject:#"zipcode"];
return locationMapping;
}
- (RKEntityMapping *)buildMapForCurrentConditions {
// Current Conditions
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:#"CurrentConditions" inManagedObjectStore:self.restKitManager.managedObjectStore];
[mapping addAttributeMappingsFromDictionary:#{
//#"stn" : #"stn",
//#"location" : #"location",
//#"stn_lat" : #"stnLatitude",
//#"stn_lon" : #"stnLongitude",
#"ob_time.text" : #"observationTime",
#"day_of_week.text" : #"dayOfWeek",
#"temp_C.text" : #"temperatureMetric",
#"temp_F.text" : #"temperatureImperial",
#"dewp_C.text" : #"dewPointMetric",
#"dewp_F.text" : #"dewPointImperial",
#"rh_pct.text" : #"relativeHumidity",
#"wnd_dir.text" : #"windDirection",
#"wnd_spd_mph.text" : #"windSpeedImperial",
#"wnd_spd_kph.text" : #"windSpeedMetric",
#"press_in.text" : #"pressureImperial",
#"press_mb.text" : #"pressureMetric",
#"wx.text" : #"conditionSummary",
#"wx_code.text" : #"conditionCode",
#"cld_cover.text" : #"cloudCover",
#"visibility_ft.text" : #"visibilityImperial",
#"visibility_m.text" : #"visibilityMetric",
#"apparent_temp_F.text" : #"feelsLikeTemperatureImperial",
#"apparent_temp_C.text" : #"feelsLikeTemperatureMetric",
#"moon_phase.text" : #"moonPhase",
#"sunrise_utc.text" : #"sunrise",
#"sunset_utc.text" : #"sunset"
}];
[mapping setIdentificationAttributes:[NSArray arrayWithObjects:#"observationTime", nil]];
return mapping;
}
- (RKEntityMapping *)buildMapForDailySummaries {
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:#"DailySummaries" inManagedObjectStore:self.restKitManager.managedObjectStore];
[mapping addAttributeMappingsFromDictionary:#{
#"summary_date.text" : #"date",
#"day_of_week.text" : #"dayOfWeek",
#"max_temp_F.text" : #"tempMaxImperial",
#"max_temp_C.text" : #"tempMaxMetric",
#"min_temp_F.text" : #"tempMinImperial",
#"min_temp_C.text" : #"tempMinMetric",
#"wnd_spd_mph.text" : #"windSpeedImperial",
#"wnd_spd_kph.text" : #"windSpeedMetric",
#"min_wnd_spd_mph.text" : #"windSpeedMinImperial",
#"min_wnd_spd_kph.text" : #"windSpeedMinMetric",
#"max_wnd_spd_mph.text" : #"windSpeedMaxImperial",
#"max_wnd_spd_kph.text" : #"windSpeedMaxMetric",
#"wnd_gust_mph.text" : #"windGustImperial",
#"wnd_gust_kph.text" : #"windGustMetric",
#"wnd_dir.text" : #"windDirection",
#"pop.text" : #"probabilityOfPrecipitation",
#"wx.text" : #"conditionSummary",
#"wx_code.text" : #"conditionCode",
#"text_description.text" : #"textDescription",
#"sunrise_utc.text" : #"sunrise",
#"sunset_utc.text" : #"sunset",
#"locations.location.zipcode.text" : #"locationZipcode"
}];
mapping.identificationAttributes = [NSArray arrayWithObjects:#"date", #"locationZipcode", nil];
[mapping addConnectionForRelationship:#"location" connectedBy:#{#"locationZipcode": #"zipcode"}];
return mapping;
}
- (RKEntityMapping *)buildMapForHourlyForecasts {
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:#"HourlyForecasts" inManagedObjectStore:self.restKitManager.managedObjectStore];
[mapping addAttributeMappingsFromDictionary:#{
#"day_of_week_utc.text" : #"dayOfWeek",
#"time_utc.text" : #"forecastTime",
#"temp_C.text" : #"temperatureMetric",
#"temp_F.text" : #"temperatureImperial",
#"dewp_C.text" : #"dewPointMetric",
#"dewp_F.text" : #"dewPointImperial",
#"app_temp_C.text" : #"feelsLikeTemperatureMetric",
#"app_temp_F.text" : #"feelsLikeTemperatureImperial",
#"rh_pct.text" : #"relativeHumidity",
#"wx.text" : #"conditionSummary",
#"wx_code.text" : #"conditionCode",
#"day_night.text" : #"dayNight",
#"pop.text" : #"probabilityOfPrecipitation",
#"sky_cov_pct.text" : #"skyCoverPercent",
#"wnd_dir.text" : #"windDirection",
#"wnd_dir_degs.text" : #"windDirectionDegrees",
#"wnd_spd_mph.text" : #"windSpeedImperial",
#"wnd_spd_kph.text" : #"windSpeedMetric",
#"locations.location.zipcode.text" : #"locationZipcode"
}];
mapping.identificationAttributes = [NSArray arrayWithObjects:#"forecastTime", #"locationZipcode", nil];
[mapping addConnectionForRelationship:#"location" connectedBy:#{#"locationZipcode": #"zipcode"}];
return mapping;
}
- (void)getMegaFeed {
for (Locations *location in self.locations) {
NSString *path = [NSString stringWithFormat:#"/feeds/demofeeds20131031/mega.php?ZIP=%#&UNITS=all",location.zipcode];
// fetch data
[self.restKitManager getObjectsAtPath:path
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSArray *mappedObjects = [mappingResult array];
NSMutableArray *validObjectIDs = [[NSMutableArray alloc] initWithCapacity:[mappedObjects count]];
for (NSManagedObject *object in mappedObjects) {
NSManagedObjectID *moID = [object objectID];
[validObjectIDs addObject:moID];
}
[self notifyObservers:#selector(megaFeedDidFinish:location:) withObject:validObjectIDs withObject:location];
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
[REUtility showDefaultAlertWithError:error];
[RELog error:#"%s Hit error:%#", __func__, error];
}];
}
}
UPDATES
#WAIN
I tried changing the code as you suggested. The current conditions object seems to properly receive a location but it is a one-to-one relationship. The others are one-to-many are are still not mapping correctly. Code changes tested are below:
[locationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"daily_summaries" toKeyPath:#"dailySummaries" withMapping:dailySummariesMapping]];
[locationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"hourly_summaries" toKeyPath:#"hourlyForecasts" withMapping:hourlyForecastsMapping]];
[locationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"sfc_ob" toKeyPath:#"currentConditions" withMapping:currentConditionsMapping]];
[currentConditionsMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"location" toKeyPath:#"location" withMapping:locationMapping]];
[dailySummariesMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"location" toKeyPath:#"location" withMapping:locationMapping]];
[hourlyForecastsMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"location" toKeyPath:#"location" withMapping:locationMapping]];
The paths are wrong for all of your relationship mappings. They currently contains something like #"locations.location.daily_summaries" but should be #"daily_summaries". You should have been seeing in the trace log that no relationships were found for the key path during the mapping process.
I need to make two different types of POST coming from the User class.
//JSON Type A
{
"password":"12345",
"email":"test#gmail.com"
}
//JSON Type B
{
"user":{
"Password":"12345",
"Email":"sample#gmail.com"
}
}
I've tried to make two request descriptors and adding them to my object manager however I get the error
"Cannot add a request descriptor for the same object class as an
existing request descriptor."
My code
#interface User : NSObject
#property (nonatomic, retain) NSString * userID;
#property (nonatomic, retain) NSString * email;
#property (nonatomic, retain) NSString * password;
#property (nonatomic, retain) NSString * firstName;
#property (nonatomic, retain) NSString * lastName;
#end
- (void)setupUserMapping:(RKObjectManager *)objectManager {
// Setup user response mappings
RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[User class]];
[userMapping addAttributeMappingsFromDictionary:#{
#"ID" :#"userID",
#"Email" : #"email",
#"Password" : #"password",
#"FirstName" : #"firstName",
#"LastName" : #"lastName",
}];
RKResponseDescriptor *responseDescriptorAuthenticate = [RKResponseDescriptor responseDescriptorWithMapping:userMapping
pathPattern:#"/Authenticate"
keyPath:nil
statusCodes:[NSIndexSet indexSetWithIndex:200]];
RKResponseDescriptor *responseDescriptorRegister = [RKResponseDescriptor responseDescriptorWithMapping:userMapping
pathPattern:#"/Register"
keyPath:nil
statusCodes:[NSIndexSet indexSetWithIndex:200]];
[objectManager addResponseDescriptor:responseDescriptorRegister];
[objectManager addResponseDescriptor:responseDescriptorAuthenticate];
// Setup user request mappings
RKObjectMapping* userRequestMappingForRegister = [RKObjectMapping requestMapping];
[userRequestMappingForRegister addAttributeMappingsFromDictionary:#{
#"email" : #"Email",
#"password" : #"Password",
#"firstName" : #"FirstName",
#"lastName" : #"LastName",
}];
RKRequestDescriptor *requestDescriptorForRegister = [RKRequestDescriptor requestDescriptorWithMapping:userRequestMappingForRegister objectClass:[User class] rootKeyPath:#"user"];
RKObjectMapping* userRequestMappingForAuthenticate = [RKObjectMapping requestMapping];
[userRequestMappingForAuthenticate addAttributeMappingsFromDictionary:#{
#"userID" :#"ID",
#"email" : #"email",
#"password": #"password"
}];
RKRequestDescriptor *requestDescriptorForAuthenticate = [RKRequestDescriptor requestDescriptorWithMapping:userRequestMappingForAuthenticate objectClass:[User class] rootKeyPath:nil];
[objectManager addRequestDescriptor:requestDescriptorForRegister];
[objectManager addRequestDescriptor:requestDescriptorForAuthenticate];
}
Does anyone know how I can solve this problem without creating a separate class for these requests?
Any help is appreciated.
Thanks.
You can use a dynamic mapping to switch the serialization behaviors. If this is a common enough issue, we could conceivably add path matching to the request descriptor. I just have not had a ton of requests for such a feature.
There is an example of how to use the dynamic mapping with a request in the unit tests: https://github.com/RestKit/RestKit/blob/master/Tests/Logic/ObjectMapping/RKObjectParameterizationTest.m#L495-L534
For multiple request descriptors, I declared a new model class with the same data members as the earlier one, and then referenced it while adding the request descriptor instead of the earlier one as follow.
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[CMAGoogleUserDataModel class]];
Here the newly created class was "CMAGoogleUserDataModel"
Noted: I am not sure whether it is the optimised one or not, but it did solve my use case.