RestKit error mapping Expedia API - ios

I am having an error when trying to map deeply nested objects from JSON with RestKit. First, I try to map hotel name which works normally. However, when I try to get price from "ChargeableRateInfo" from JSON I get following error:
restkit.object_mapping:RKMappingOperation.m:652 WARNING: Failed
mapping nested object: (null)
Here is JSON example which I am trying to map:
{
"HotelListResponse": {
"customerSessionId": "0ABAAA3E-7637-1591-4F62-5548AC792D42",
"numberOfRoomsRequested": 1,
"moreResultsAvailable": true,
"cacheKey": "-48763715:14f65548ac7:2d4e",
"cacheLocation": "10.186.170.62:7300",
"cachedSupplierResponse": {
"#supplierCacheTolerance": "NOT_SUPPORTED",
"#cachedTime": "0",
"#supplierRequestNum": "235",
"#supplierResponseNum": "1",
"#supplierResponseTime": "503",
"#candidatePreptime": "30",
"#otherOverheadTime": "5",
"#tpidUsed": "5001",
"#matchedCurrency": "true",
"#matchedLocale": "true"
},
"HotelList": {
"#size": "1",
"#activePropertyCount": "235",
"HotelSummary": {
"#order": "0",
"#ubsScore": "1069912",
"hotelId": 206005,
"name": "Mediterranean Inn",
"address1": "425 Queen Anne Ave N",
"city": "Seattle",
"stateProvinceCode": "WA",
"postalCode": 98109,
"countryCode": "US",
"airportCode": "SEA",
"supplierType": "E",
"propertyCategory": 1,
"hotelRating": 3,
"confidenceRating": 70,
"amenityMask": 7799051,
"tripAdvisorRating": 4,
"tripAdvisorReviewCount": 1086,
"tripAdvisorRatingUrl": "http://www.tripadvisor.com/img/cdsi/img2/ratings/traveler/4.0-12345-4.gif",
"locationDescription": "Near Pacific Northwest Ballet",
"shortDescription": "<p><b>Property Location</b> <br />With a stay at Mediterranean Inn, you&apos;ll be centrally located in Seattle, steps from Key Arena and minutes from Seattle Children&apos;s Theatre. This family-friendly",
"highRate": 270,
"lowRate": 162,
"rateCurrencyCode": "USD",
"latitude": 47.62244,
"longitude": -122.35672,
"proximityDistance": 12.288694,
"proximityUnit": "MI",
"hotelInDestination": true,
"thumbNailUrl": "/hotels/1000000/900000/897600/897598/897598_108_t.jpg",
"deepLink": "http://www.travelnow.com/templates/55505/hotels/206005/overview?lang=en&currency=USD&standardCheckin=9/15/2015&standardCheckout=9/17/2015&roomsCount=1&rooms[0].adultsCount=2",
"RoomRateDetailsList": {
"RoomRateDetails": {
"roomTypeCode": 15567,
"rateCode": 15567,
"maxRoomOccupancy": 2,
"quotedRoomOccupancy": 2,
"minGuestAge": 0,
"roomDescription": "Petite Room, 1 Queen Bed, Kitchenette",
"propertyAvailable": true,
"propertyRestricted": false,
"expediaPropertyId": 897598,
"RateInfos": {
"#size": "1",
"RateInfo": {
"#priceBreakdown": "true",
"#promo": "true",
"#rateChange": "false",
"RoomGroup": {
"Room": {
"numberOfAdults": 2,
"numberOfChildren": 0,
"rateKey": "81423e04-b223-4ea1-807a-8812c13b31be",
"ChargeableNightlyRates": [
{
"#baseRate": "270.00",
"#rate": "162.00",
"#promo": "true"
}, {
"#baseRate": "270.00",
"#rate": "162.00",
"#promo": "true"
}
]
}
},
"ChargeableRateInfo": {
"#averageBaseRate": "270.00",
"#averageRate": "162.00",
"#commissionableUsdTotal": "324.00",
"#currencyCode": "USD",
"#maxNightlyRate": "162.00",
"#nightlyRateTotal": "324.00",
"#grossProfitOffline": "54.87",
"#grossProfitOnline": "81.40",
"#surchargeTotal": "69.07",
"#total": "393.07",
"NightlyRatesPerRoom": {
"#size": "2",
"NightlyRate": [
{
"#baseRate": "270.00",
"#rate": "162.00",
"#promo": "true"
}, {
"#baseRate": "270.00",
"#rate": "162.00",
"#promo": "true"
}
]
},
"Surcharges": {
"#size": "1",
"Surcharge": {
"#type": "TaxAndServiceFee",
"#amount": "69.07"
}
}
},
"nonRefundable": false,
"rateType": "MerchantStandard",
"promoId": 212134483,
"promoDescription": "24 hour deal: save 40%",
"promoType": "Standard",
"currentAllotment": 3
}
}
}
}
}
}
}
}
And here is my code where I try to map this JSON:
- (void)configureRestKit
{
// initialize AFNetworking HTTPClient
NSURL *baseURL = [NSURL URLWithString:#"http://api.ean.com"];
AFHTTPClient *client = [[AFHTTPClient alloc] initWithBaseURL:baseURL];
// initialize RestKit
RKObjectManager *objectManager = [[RKObjectManager alloc] initWithHTTPClient:client];
// setup object mappings
RKObjectMapping *hotelMapping = [RKObjectMapping mappingForClass:[HotelList class]];
[hotelMapping addAttributeMappingsFromArray:#[#"name"]];
RKObjectMapping *priceMapping = [RKObjectMapping mappingForClass:[ChargeableRateInfo class]];
[priceMapping addAttributeMappingsFromDictionary:#{#"total": #"total"}];
[hotelMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"RoomRateDetailsList.RoomRateDetails.RateInfos.RateInfo.ChargeableRateInfo" toKeyPath:#"chargeableRateInfo" withMapping:priceMapping]];
// register mappings with the provider using a response descriptor
RKResponseDescriptor *responseDescriptor =
[RKResponseDescriptor responseDescriptorWithMapping:hotelMapping
method:RKRequestMethodGET
pathPattern:#"/ean-services/rs/hotel/v3/list"
keyPath:#"HotelListResponse.HotelList.HotelSummary"
statusCodes:[NSIndexSet indexSetWithIndex:200]];
[objectManager addResponseDescriptor:responseDescriptor];
}
Does anyone know how to solve this? Thanks

The mapping key should be #total based on the content in the JSON.
This isn't ideal however as # is a special character in KVC which RestKit uses heavily. You will need to work around this with something from this answer or this pull request.

Related

Couchbase N1QL query running, but no data retrieved (wrong query i guess)

I am trying to retrieve data from 2 buckets, no error but nothing shows up (I do have documents I need in these buckets).
1st bucket: a_bucket
here is the document I am interested in (I do have 3 different docs)
author_ID document:
{
"author_ID": 1,
"profil_creation_date": "2017/01/01/01:23:05/+5",
"prefix": "Mr.",
"first_name": "Dylan",
"middle_name_s": "Alfred",
"last_name": "Kerr",
"date_of_birth": "1974/01/02",
"sex": "M",
"marital_status": "Single",
"mobile_phone": "(860) 231-3336",
"address": [
{
"address_1": {
"address_ID": 1,
"home_address": "338 Counts Lane",
"city": "West Hartford",
"province/state": "CT",
"postal_code": "06105"
}
},
{
"address_2": {
"address_ID": 2,
"work_address": "977 Copperhead Rd",
"city": "Newington",
"province/state": "CT",
"postal_code": "06111"
}
}
]
}
2nd bucket: b_bucket
here are the 2 docs I am interested in:
p_output_ID document:
{
"p_output_ID": 1,
"author_ID": 2,
"overall_score": 4.41,
"status": {
"r_status_first": "TRUE",
"r_status_second": "FALSE",
"r_status_third": "YES",
"y_status_second": "TRUE",
"y_status_third": "FALSE",
"g_status_third": "TRUE"
}
}
timing_ID document:
{
"timing_ID": 1,
"p_output_ID": 1,
"author_ID": 1,
"date_and_time": "2017-06-06/23:45:25.25/+5",
"time_in_seconds": 12525,
"incremental_time_in_seconds": "time_in_seconds",
"current_state_and_duration": {
"state": "RED",
"duration_in_seconds": 33333
}
}
my goal is to grab these informations in one query ():
prefix, first_name, middle_name_s, last_name (from author_ID document in a_bucket)
overall_score (from p_output_ID document in b_bucket)
date_and_time, state (from timing_ID document in b_bucket)
Here is my query:
select p2.current_state_and_duration.state, p1.overall_score, p2.date_and_time
from proc_data_bucket p1 USE KEYS "p_output_ID"
JOIN proc_data_bucket p2 ON KEYS "author_ID";
The syntax is OK, but I am getting no data
Please help me with that...
CREATE INDEX ix1 ON b_bucket(timing_ID);
SELECT p1.prefix, p1.first_name, p1.middle_name_s, p1.last_name,
p2.date_and_time,p2.state,
p3.overall_score
FROM b_bucket p2
JOIN a_bucket p1 ON KEYS ("author_" || TO_STRING(p2.author_ID))
JOIN b_bucket p3 ON KEYS ("p_output_" || TO_STRING(p2.p_output_ID))
WHERE p2.timing_ID BETWEEN 10 AND 50;

Rendering few models in gson

Is it possible to send few objects into gson rendering view?
I tried to use in controller:
respond trainings, [status: OK, view:"trainingsByClients", model: [myVariable: "test", anotherVariable: 123]]
and in gson view:
model {
Iterable<Training> trainingList
String myVariable
}
json {
myVariable myVariable
trainings tmpl.training(trainingList ?: [])
}
and it responds with:
{
"myVariable": null,
"trainings": [
{
"id": 3,
"name": "test t",
"numberOfAbsentClients": 0,
"startDate": "2016-11-20T09:00:00+0000",
"numberOfClients": 2,
"section": {
"id": 1,
"name": "test sec"
}
},
{
"id": 10,
"name": "test 2",
"numberOfAbsentClients": 0,
"startDate": "2016-11-09T11:00:00+0000",
"numberOfClients": 2,
"section": {
"id": 2,
"name": "sec 2"
}
}
]
}
ok, I found solution:
render(view: "trainingsByClients", model: [trainingList: trainings, myVariable: "asdg"])
so we should use render instead of respond. Respond is not adding properly additional model objects.
Actually you have to use the g.render method for all model properties when using respond
example gson view:
model {
Iterable<Training> trainingList
String myVariable
}
json {
myVariable g.render(myVariable)
trainings tmpl.training(trainingList ?: [])
}
This is only related to the models you parse in the respond.model parameter

RestKit Dynamic Mapping with heterogeneouse collections and Core Data

I have the following JSON structure which I want to map with RestKit in CoreData:
[
{
"id": 1,
"type": "folder",
"name": "Folder 1",
"children": [
{
"id": 1,
"type": "document",
"name": "Document 1"
},
{
"id": 2,
"type": "folder",
"name": "Folder 2",
"children": [
{
"id" : 2,
"type": "document",
"name": "Document 2"
}
]
}
]
}
]
It's a typical filesystem structure where a folder can contain other folders or documents.
I have two CoreData entities: Folder and Document.
The Folder entity has the following relationships:
Here is my Mapping Code:
RKEntityMapping* foldersMapping = [RKEntityMapping mappingForEntityForName:#"Folder" inManagedObjectStore:managedObjectStore];
foldersMapping.identificationAttributes = #[ #"objectId" ];
[foldersMapping addAttributeMappingsFromDictionary:#{#"id" : #"objectId",
#"name" : #"name"}];
RKEntityMapping* documentsMapping = [RKEntityMapping mappingForEntityForName:#"Document" inManagedObjectStore:managedObjectStore];
documentsMapping.identificationAttributes = #[ #"objectId" ];
[documentsMapping addAttributeMappingsFromDictionary:#{#"id" : #"objectId",
#"name" : #"name"}];
In my dynamic mapping I use the type of the object to decide if it is a document or a folder.
RKDynamicMapping* dynamicMapping = [RKDynamicMapping new];
[dynamicMapping setObjectMappingForRepresentationBlock:^RKObjectMapping *(id representation) {
if ([[representation valueForKey:#"type"] isEqualToString:#"Folder"]) {
return foldersMapping;
} else if ([[representation valueForKey:#"type"] isEqualToString:#"Document"]) {
return documentsMapping;
}
return nil;
}];
Now I added the dynamic mapping as relationship mapping to the folder mapping for the property children two times.
[foldersMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"children" toKeyPath:#"children" withMapping:dynamicMapping]];
[foldersMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"children" toKeyPath:#"documents" withMapping:dynamicMapping]];
And here is my Problem: All this works, but I got the following Warnings:
RestKitNesterFolderTestProject[9969:3a03] W
restkit.object_mapping:RKMappingOperation.m:554 WARNING: Failed
mapping nested object: The operation couldn’t be completed. (Cocoa
error 1550.)
I think the Problem is that I doubled the mapping for the relationship children, so RestKit tries to map each child two times, once as Folder and once as Document.
Any help is appreciated.

Multi root with a GET request

I am searching a way to map 2 different arrays with the following JSON
"establishments": [
{
"id": 1,
"name": "Boudin Sourdough Bakery and Cafe",
"address": "1 Stockton St",
"zip_code": "94108",
"city": "San Francisco",
"country": "us",
"phone_number": "",
"position": {
"latitude": 37.78590500000001,
"longitude": -122.406289
},
"distance_from": 13.75783097391759
}
],
"places": [
{
"id": 3,
"name": "Private Place",
"position": {
"latitude": 37.78583400000001,
"longitude": -122.406417
},
"is_private": false,
"distance_from": 0
}
]
I have 2 different mappings. One for establishment and another for place. I found some help with the documentation but it's working only for PUT or POST requests.
Currently, I have the following request
[session.objectManager getObjectsAtPathForRouteNamed:APICallSearchSearchPlaceRouteName object:nil parameters:params success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSArray * resultPlaces=mappingResult.array;
[delegate APICallDidSearch:resultPlaces];
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSError* myError=[self catchCustomErrorInRKError:error setDomain:#"Search"];
if (myError.code == NSURLErrorCancelled) return;
[delegate APICallDidFailSearch:myError];
}];
Mapping & Mapping Descriptor
RKEntityMapping *establishmentMapping = [APICallEstablishment RKGetEstablishmentMappingForManagedObjectStore:self.appDelegate.managedObjectStore];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:establishmentMapping
method:RKRequestMethodGET
pathPattern:APICallSearchSearchPlacePattern
keyPath:#"establishments"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[session.objectManager addResponseDescriptor:responseDescriptor];
I tried to adapt the example to my case with that (just before my request written above)
RKManagedObjectRequestOperation *operation = [session.objectManager appropriateObjectRequestOperationWithObject:establishmentMapping method:RKRequestMethodGET path:#"/whatever" parameters:nil];
operation.targetObject = nil;
//Does it still exist ? (XCode says an error)
//operation.targetObjectID = nil;
[session.objectManager enqueueObjectRequestOperation:operation];
Thank you in advance for your help.
Using session.objectManager getObjectsAtPathForRouteNamed... is a good option (better than trying to use RKManagedObjectRequestOperation directly).
You need to create another response descriptor, like responseDescriptor, but for places key path and mapping.

Core Data - Sort descriptor for parent / child hierarchy

I get a JSON object from a HTML request which contains a hierarchical structure.
Sample from JSON object:
{
"_id": "4f870f064f95ae0da8000002",
"name": "Category",
"parent_id": null
},
{
"_id": "4f870f0e4f95ae0da8000004",
"name": "Brand",
"parent_id": null
},
{
"_id": "4f8715bd4f95ae0da8000028",
"name": "Dermalogica",
"parent_id": "4f870f0e4f95ae0da8000004"
},
{
"_id": "4f8715de4f95ae0da800002a",
"name": "Molton Brown",
"parent_id": "4f870f0e4f95ae0da8000004"
},
{
"_id": "4f8715ea4f95ae0da800002c",
"name": "Waxing",
"parent_id": "4f870f064f95ae0da8000002"
},
{
"_id": "4f8715f34f95ae0da800002e",
"name": "Mens Hair",
"parent_id": "4f870f064f95ae0da8000002"
},
{
"_id": "4f8715fd4f95ae0da8000030",
"name": "Ladies Hair",
"parent_id": "4f870f064f95ae0da8000002"
},
{
"_id": "4f87161f4f95ae0da8000032",
"name": "Massage",
"parent_id": "4f870f064f95ae0da8000002"
}
When I save it in the same way in just one entity, how would I define the fetch request (sorting) so that the objects are sorted with their parent / child relation ?
There is no way to sort this kind of data using sortDescriptors.
This is how I solve the problem, having comments of article with threaded style discussions. After I download all comments, I need to reindexComments
-(void)reindexComments{
NSArray *articleComments = self.comments.allObjects;
[self fetchChildsWithComments:articleComments forParentId:0 num:1];
}
-(NSUInteger)fetchChildsWithComments:(NSArray*)articleComments forParentId:(NSUInteger)parentId num:(NSUInteger)num{
NSArray *childComments = [articleComments filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"parentId == %u", parentId]];
childComments = [childComments sortedArrayUsingComparator:^NSComparisonResult(Comment *c1, Comment *c2) {
if (c1.commentIdValue < c2.commentIdValue){
return NSOrderedAscending;
}else{
return NSOrderedDescending;
}
}];
for (Comment *newRootComment in childComments){
newRootComment.numValue = num;
num++;
num = [self fetchChildsWithComments:articleComments forParentId:newRootComment.commentIdValue num:num];
}
return num;
}
and finally I just sort by numValue field to get my nice threaded discussion
One Way - to use NSOrderedSet - http://developer.apple.com/library/mac/#documentation/Foundation/Reference/NSOrderedSet_Class/Reference/Reference.html
Second,more convinient(since NSOrderedSet is introduced in iOS 5),just a soimple NSFetchRequest with sortDescriptors.Since it is an Array, you can use a many descritors at a time as you want. So, using descriptors by parent_id and id should give you desired result.
NSFetchRequest *request = [[NSFetchRequest alloc]init];
request.entity = [NSEntityDescription entityForName:#"Child" inManagedObjectContext:context];
// request.predicate = [NSPredicate predicateWithFormat:#"parent_id = %#",parent_ID];You don't need any predicate,right?
request.sortDescriptors = [NSArray arrayWithObjects:[NSSortDescriptor sortDescriptorWithKey:#"parent_id" ascending:YES],[NSSortDescriptor sortDescriptorWithKey:#"_id" ascending:YES],nil];
return [context executeFetchRequest:request error:&error];
And,in Objective-C it's not convinient to use underlines in names.
Hope, that helps.

Resources