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.
JSON
{
"entity": {
"id": 1,
"name": "name"
},
"settings": {
"key": "value"
}
}
Entity Model
Attributes: id, name
Relationship: settings(one to one)
Settings Model
Attributes: key
Relationship: entity(reverse relationship, one to one)
EntityMapping
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:NSStringFromClass([self class]) inManagedObjectStore:[RKManagedObjectStore defaultStore]];
mapping.persistentStore = [RKManagedObjectStore defaultStore].persistentStoreCoordinator.persistentStores.firstObject;
mapping.identificationAttributes = #[#"id"];
[mapping addAttributeMappingsFromDictionary:#{
#"id" : #"id",
#"name" : #"name"
}];
Had settings been inside entity in json response, I would add this
[mapping addPropertyMapping:
[RKRelationshipMapping relationshipMappingFromKeyPath:#"settings"
toKeyPath:#"settings" withMapping:[Settings map]]
];
But my response id different than this, so how should I map correctly?
Both entity and settings are in a container dictionary, so you can change your response descriptor to not drill down so far and then use key paths to drill into the entity part and have direct access to the settings part:
[mapping addAttributeMappingsFromDictionary:#{
#"entity.id" : #"id",
#"entity.name" : #"name"
}];
[mapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"settings" toKeyPath:#"settings" withMapping:[Settings map]]];
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
How cant I set a static value while mapping entities?
I have a JSON response like this:
"friends": [
{
"id": 123,
"name": "Friend",
},
]
"featured": [
{
"id": 456,
"name": "Some Featured user",
},
]
My mapping and descriptors look like this:
RKMapping *friendsMapping = [ProjectMappingProvider userMapping];
RKMapping *featuredMapping = [ProjectMappingProvider featuredUserMapping];
RKResponseDescriptor *friendsResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:friendsMapping
method:RKRequestMethodGET
pathPattern:#"/api/users"
keyPath:#"friends"
statusCodes:statusCodeSet];
RKResponseDescriptor *featuredResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:friendsMapping
method:RKRequestMethodGET
pathPattern:#"/api/users"
keyPath:#"featured"
statusCodes:statusCodeSet];
RKManagedObjectRequestOperation *operation = [[RKManagedObjectRequestOperation alloc] initWithRequest:request
responseDescriptors:#[
friendsResponseDescriptor,
featuredResponseDescriptor]];
... some code emited for readabilty ...
Now mu friendsResponseDescriptor and featuredResponseDescriptors look almost identical, but I would like to set additional CoreData parameter accordingly. Objects mapped through friendsDescriptor should have section = 0 and objects mapped through featured descriptor should have section = 10.
So, can I do something like this?
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:#"User"
inManagedObjectStore:[[DataModel sharedDataModel] objectStore]];
[mapping addAttributeMappingsFromDictionary:#{
#"id": #"userId",
#"name": #"name" }];
mapping.identificationAttributes = #[ #"userId" ];
// How can I do somethning like this?
[mapping setValue:#0 forKey:#"section"];
And the featured mapping:
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:#"User"
inManagedObjectStore:[[DataModel sharedDataModel] objectStore]];
[mapping addAttributeMappingsFromDictionary:#{
#"id": #"userId",
#"name": #"name" }];
mapping.identificationAttributes = #[ #"userId" ];
// How can I do somethning like this?
[mapping setValue:#10 forKey:#"section"];
Note that I don't have any other indicator whetever user is a friend or featured in the user JSON itself. The only way I can distinguish the type of user (friend,featured) is in which list in JSON response the user is set.
I am later using the section property in the table view controller to have sections.
If you are using different entities, set default values on them. If you aren't using different entities, consider changing so that you are (they could be sub-entities of a common parent).
You can't insert data into the mapping. The mapping only describes what RestKit should process. To edit the values you would need to get involved in the mapping process yourself and implement some delegate methods to inject additional data.
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