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.
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
I'm doing a GET request with RESTKit, and I need sone help mapping the JSON response.
Here is the response that I need to map:
{"limit_hit":false,"providers":
[{"id":876553,
"name":"Cooper, Bradley N, DDS",
"specialty_groups":["Other Provider"],
"tags":[],
"has_comments":false,
"number_of_comments":0,
"locations":
[{"address":"1234 Rifle Range Road, El Cerrito, CA, 94530",
"providers_at_address_count":1,
"client_product_count":0,
"non_client_product_count":2,
"address_lines":["1234 Rifle Range Road, El Cerrito, CA, 94530"],
"address_id":234578,
"specialty_groups":
[{"specialty_group":"Other Provider"}],
"provider_types":
[{"provider_type":"Other Provider"}]},
{"address":"7501 Mission Rd, Shawnee Mission, KS, 66208",
"providers_at_address_count":2,
"client_product_count":0,
"non_client_product_count":2,
"address_lines":["7654 Main S, El Cerrito, CA, 94530"],
"address_id":654432,
"specialty_groups":
[{"specialty_group":"Other Provider"}],
"provider_types":
[{"provider_type":"Other Provider"}]
}]
}]
}
I want to be able to map both addresses, but I don't know how. All I'm able to do currently is map the id, name, has_comments, and number_of_comments (I'm using the keypath of "providers").
Here is my current mapping provider:
+ (RKMapping *)searchMapping
{
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[ProviderSearch class]];
[mapping addAttributeMappingsFromDictionary:#{
#"id": #"doctorID",
#"name": #"name",
}];
return mapping;
}
What exactly am I doing wrong, and how do I fix it?
Create another method to return the mapping for locations and then associate that mapping to this original one. Like this:
// ProviderLocation.m
+ (RKObjectMapping *)objectMapping
{
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[ProviderLocation class]];
[mapping addAttributeMappingsFromDictionary:#{
#"address": #"address",
...
}];
return mapping;
}
Relationship:
+ (RKObjectMapping *)searchMapping
{
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[ProviderSearch class]];
[mapping addAttributeMappingsFromDictionary:#{
#"id": #"doctorID",
#"name": #"name",
}];
RKObjectMapping *locationsMapping = [ProviderLocation objectMapping];
[mapping addPropertyMapping:
[RKRelationshipMapping relationshipMappingFromKeyPath:#"locations" toKeyPath:#"locations" withMapping:locationsMapping]];
return mapping;
}
Just remember to create a NSArray property in ProviderLocation.h named locations.
i've never used RKObjectMapping before but the "locations" you have there are an array of dictionary objects. so you would need an
NSArray loc = [myJson objectForKey:#"locations"];
for(NSDictionary *dict in loc){
//here each dict obj will have your "address", "providers_at_address_count" and etc... so if you want to access any of them you can call...
NSString *addr = [dict objectForKey:#"address"];
}
now somehow convert that to what you are doing with RXObjectMapping and you are golden =P
how can i map nested objects of the same type?
i have an xml with objects that may contain multiple objects of the same type:
<entry location="l1">
<entry location="l1.1">
<entry location="l1.1.1">
</entry>
<entry location="l1.1.2">
</entry>
</entry>
</entry>
i get an infinite recursion if i add a propertymapping with the same mapping:
+ (RKObjectMapping *)objectMapping {
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[Entry class]];
[mapping addAttributeMappingsFromDictionary:#{#"location": #"location"}];
[mapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"nextEntries"
toKeyPath:#"entries"
withMapping:[Entry objectMapping]]];
return mapping;
}
is it possible to add the sub-objects to an array of each parent object?
cheers
//edit: following code works for me:
+ (RKObjectMapping *)objectMapping
{
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[Entry class]];
[mapping addAttributeMappingsFromDictionary:#{#"location": #"location"}];
RKObjectMapping *innerMapping = [RKObjectMapping mappingForClass:[Entry class]];
[innerMapping addAttributeMappingsFromDictionary:#{#"location": #"location"}];
[mapping addPropertyMapping:[RKRelationshipMapping
relationshipMappingFromKeyPath:#"entry"
toKeyPath:#"entries"
withMapping:innerMapping]];
return mapping;
}
I haven't tried it so I don't know if it will work but try this:
+ (RKObjectMapping *)objectMapping
{
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[Entry class]];
[mapping addAttributeMappingsFromDictionary:#{#"location": #"location"}];
RKObjectMapping *innerMapping = [RKObjectMapping mappingForClass:[Entry class]];
[innerMapping addAttributeMappingsFromDictionary:#{#"location": #"location"}];
[mapping addPropertyMapping:[RKRelationshipMapping
relationshipMappingFromKeyPath:#"nextEntries"
toKeyPath:#"entries"
withMapping:innerMapping]];
return mapping;
}
Not sure that toKeyPath:#"entries" is correct, it may need to be toKeyPath:#"entry" based on your XML.
The infinite recursion is because you're calling the same method from itself ([Entry objectMapping]), it has nothing to do with the mapping as such.
In RestKit for mapping a class to JSON key-value we use,
RKObjectMapping * userMapping = [RKObjectMapping mappingForClass:[User class]];
[userMapping mapKeyPath:#"PrimaryKey" toAttribute:#"id"];
[[RKObjectManager sharedManager].mappingProvider setMapping:usereMapping forKeyPath:#"clientUser"];
However in android, if restlet is used, we just have to add #JsonProperty("PrimaryKey"), when declaring the variable in the class. And the Key value mapping is done.
Is there a simpler way for iOS restkit similar to android restlet?
Thanks in advance.
You can do it like this in ios
RKObjectMapping* articleMapping = [RKObjectMapping mappingForClass:[Article class]];
[articleMapping addAttributeMappingsFromDictionary:#{
#"title": #"title",
#"body": #"body",
#"author": #"author",
#"publication_date": #"publicationDate"
}];
And
// Create our new Author mapping
RKObjectMapping* authorMapping = [RKObjectMapping mappingForClass:[Author class] ];
// NOTE: When your source and destination key paths are symmetrical, you can use addAttributesFromArray: as a shortcut instead of addAttributesFromDictionary:
[authorMapping addAttributeMappingsFromArray:#[ #"name", #"email" ]];
REFER HERE