RESTKIT .20.1 nested array with relationship mapping assistance - restkit-0.20

I am having trouble with RESTKIT and mapping the following JSON via RESTKit .20.1. I have a nested JSON array and I am trying to map via the RelationshipMapper. I am able to map the 5 chart_data object successfully but the related nested chart_datatum objects do not map to the ChartDataDetails class. All of the properties are blank.
I would like the following object structure.
Alert Instance
Array of ChartData objects
Each ChartData object should contain 1 ChartDataDetails object <---- this object is blank
Please see the link at the bottom for the screenshot of the XCode Debugger for the current state.
Can someone please provide some assistance?
Thanks,
G
{
"alert_instance": {
"alert_template_id": 1,
"alert_time_period": "2013-05-29",
"chart_data": [
{
"chart_datum": {
"alert_instance_id": 1,
"chart_data_id": 1,
"chart_data_name": "provider_id",
"datatype": 1,
"chart_data_value": "1"
}
},
{
"chart_datum": {
"alert_instance_id": 1,
"chart_data_id": 1,
"chart_data_name": "Provider_name",
"datatype": 2,
"chart_data_value": "Stubbs, Drew"
}
},
{
"chart_datum": {
"alert_instance_id": 1,
"chart_data_id": 1,
"chart_data_name": "Posting_date",
"datatype": 3,
"chart_data_value": "05/12/2013"
}
},
{
"chart_datum": {
"alert_instance_id": 1,
"chart_data_id": 1,
"chart_data_name": "Charges",
"datatype": 5,
"chart_data_value": "229"
}
},
{
"chart_datum": {
"alert_instance_id": 1,
"chart_data_id": 1,
"chart_data_name": "Payments",
"datatype": 5,
"chart_data_value": "-1023.11"
}
}
]
}
Model classes
AlertInstance.h
#interface AlertInstance : NSObject
#property (nonatomic, copy) NSNumber* alertTemplateId;
#property (nonatomic, copy) NSDate* alertTimePeriod;
#property (nonatomic, retain) NSMutableArray* chartData;
#end
ChartData.h
#interface ChartData : NSObject
#property (nonatomic, retain) ChartDataDetails *chartDataDetails;
#end
ChartDataDetails.h
#interface ChartDataDetails : NSObject
#property (nonatomic, copy) NSNumber* chartDataId;
#property (nonatomic, copy) NSString* chartDataName;
#property (nonatomic, copy) NSNumber* chartDataType;
#property (nonatomic, copy) NSNumber* chartDataValue;
#end
code for mappings..
RKObjectMapping* chartDataDetailMapping = [RKObjectMapping mappingForClass:[ChartDataDetails class]];
[chartDataDetailMapping addAttributeMappingsFromDictionary:#{
#"chart_data_id": #"chartDataId",
#"chart_data_name": #"chartDataName",
#"datatype": #"chartDataType",
#"chart_data_value": #"chartDataValue",
}];
RKObjectMapping* chartDataMapping = [RKObjectMapping mappingForClass:[ChartData class]];
[chartDataMapping addAttributeMappingsFromDictionary:#{
#"chart_data":#"chartData"}];
RKObjectMapping* alertInstanceMapping = [RKObjectMapping mappingForClass:[AlertInstance class]];
[alertInstanceMapping addAttributeMappingsFromDictionary:#{
#"alert_template_id": #"alertTemplateId",
#"alert_time_period": #"alertTimePeriod",
}];
[alertInstanceMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"chart_data"
toKeyPath:#"chartData"
withMapping:chartDataMapping]];
[alertInstanceMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"chart_datum"
toKeyPath:#"chartDataDetails"
withMapping:chartDataDetailMapping]];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:alertInstanceMapping
pathPattern:nil
keyPath:#"alert_instance"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
NSURL *URL = [NSURL URLWithString:#"http://ideainnovationsmobiledev1.cloudapp.net:3000/alert_instances/1.json"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
RKObjectRequestOperation *objectRequestOperation = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:#[ responseDescriptor ]];
[objectRequestOperation setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult)
link to screen shot of the debugger
https://docs.google.com/file/d/0Bwaj6O1rXiKaeFIwT2s4Rm1Gb00/edit?usp=sharing
EDIT:
Ok I figured it out.
I needed to chain the ChartDetailsMapping to the ChartData as follows.
[chartDataMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"chart_datum"
toKeyPath:#"chartDataDetails"
withMapping:chartDataDetailMapping]];
[alertInstanceMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"chart_data"
toKeyPath:#"chartData"
withMapping:chartDataMapping]];

Related

Using Mantle with Sickbeard API

I'm trying to map the responses from the Sickbeard API to my objects with Mantle, but I can't figure out how, since the response is key-value based using TVDB id's as key, like this:
"data": {
"71663": {
"air_by_date": 0,
"cache": {
"banner": 1,
"poster": 1
},
"language": "en",
"network": "FOX",
"next_ep_airdate": "2014-09-28",
"paused": 0,
"quality": "Any",
"show_name": "The Simpsons",
"status": "Continuing",
"tvdbid": 71663,
"tvrage_id": 6190,
"tvrage_name": "The Simpsons"
},
"72227": {
"air_by_date": 0,
"cache": {
"banner": 1,
"poster": 1
},
"language": "en",
"network": "CBS",
"next_ep_airdate": "",
"paused": 0,
"quality": "Any",
"show_name": "Two and a Half Men",
"status": "Continuing",
"tvdbid": 72227,
"tvrage_id": 6454,
"tvrage_name": "Two and a Half Men"
}
}
Since the data object does not simply contain an array of objects like this [{"key": value},{"key": value}] but instead objects keyed by some unique id, I'm not sure how I should map it into my SBShow classes, defined like:
#import <Foundation/Foundation.h>
#import <Mantle.h>
#interface SBShow : MTLModel <MTLJSONSerializing>
#property (nonatomic, strong) NSNumber *tvdbid;
#property (nonatomic, strong) NSString *showName;
#property (nonatomic, strong) NSString *network;
#property (nonatomic, strong) NSString *status;
#end
#implementation SBShow
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
return #{#"showName": #"show_name"};
}
+ (NSValueTransformer *)dateJSONTransformer {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"yyyy-MM-dd"];
return [MTLValueTransformer reversibleTransformerWithForwardBlock:^(NSString *str) {
return [formatter dateFromString:str];
} reverseBlock:^(NSDate *date) {
return [formatter stringFromDate:date];
}];
}
#end
Any help would be greatly appreciated.
You can do it like this, by adding the key to the rest of the 'JSON dictionary':
NSMutableArray *shows = [NSMutableArray array];
// data is an NSDictionary, representing the 'data' key in the JSON
[data enumerateKeysAndObjectsUsingBlock:^(NSString *tvdbID, NSDictionary *showData, BOOL *stop) {
NSMutableDictionary *modelDictionary = [showData mutableCopy];
modelDictionary[#"tvdbid"] = tvdbID;
NSError *error = nil;
SBShow *show = [MTLJSONAdapter modelOfClass:SBShow.class
fromJSONDictionary:modelDictionary
error:&error];
[shows addObject:show];
}];
NSLog(#"Show models are %#", shows);
You can write your own transformer to encapsulate this logic and apply it to the data key if appropriate.

RestKit - Not key value coding-compliant

I have a managed object MyEntity that I use without any problem throughout my application.
#interface MyEntity : NSManagedObject
#property (nonatomic, retain) NSString *code;
#property (nonatomic, retain) NSNumber *def_value;
#property (nonatomic, retain) NSString *label;
#property (nonatomic, retain) NSString *link_label;
#property (nonatomic, retain) NSNumber *order;
#end
I have a web service http://my_server/MyEntity which returns me:
[
{
CODE: "TYPE",
LABEL: "Administrative",
ORDER: "3",
DEF_VALUE: "0",
LINK_LABEL: ""
},
{
CODE: "TOPIC",
LABEL: "NDF",
ORDER: "1",
DEF_VALUE: "0",
LINK_LABEL: "Administrative"
},
{
CODE: "TOPIC",
LABEL: "Report",
ORDER: "2",
DEF_VALUE: "1",
LINK_LABEL: "Administrative"
},
{
CODE: "TOPIC",
LABEL: "Other",
ORDER: "3",
DEF_VALUE: "0",
LINK_LABEL: "Administrative"
}, ...
]
I'm trying to map and fetch this list
NSURL *baseUrl = [NSURL URLWithString: #"http://my_server"];
NSString *objName = #"MyEntity";
NSManagedObjectModel *managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
[RKManagedObjectStore setDefaultStore:managedObjectStore];
[managedObjectStore createPersistentStoreCoordinator];
RKObjectManager *objectManager = [RKObjectManager managerWithBaseURL: baseUrl];
RKEntityMapping *objMap = [RKEntityMapping mappingForEntityForName:objName inManagedObjectStore:managedObjectStore];
[objMap addAttributeMappingsFromDictionary: #{
#"CODE": #"code",
#"LABEL": #"label",
#"ORDER": #"order",
#"DEF_VALUE": #"def_value",
#"LINK_LABEL": #"link_label",
}];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:objMap pathPattern:nil keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptor:responseDescriptor];
[objectManager getObjectsAtPath:objName parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"ok");
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"ko");
}];
It's giving me this error
I restkit.network:RKHTTPRequestOperation.m:185 GET'http://my_server/MyEntity'(200 OK)
CoreData: error: Failed to call designated initializer on NSManagedObject class 'MyEntity'
Terminating app due to uncaught exception 'NSUnknownKeyException', reason:
'[ valueForUndefinedKey:]: the entity (null) is not key value coding-compliant for the key "link_label".'
I saw this error on 2 or 3 SO questions but each with a different problem and seem not any of mine.
Can someone help me to debug that ?
You need to fully configure the objectManager:
objectManager.managedObjectStore = managedObjectStore;
You also aren't completing the Core Data stack initialisation:
[managedObjectStore createManagedObjectContexts];

RestKit 0.20 - Dynamic nested object mapping

I'm working on Objective C project, that uses RestKit framework for parsing JSON responses.
Now I need some help with object mapping settings for following case:
JSON response:
{
"data": {
"SOME.API.Auth": {
"maxVersion": 2,
"minVersion": 1,
"path": "auth.cgi"
},
"SOME.Station": {
"maxVersion": 1,
"minVersion": 1,
"path": "Station/task.cgi"
}
},
"success": true
}
and following objects:
#interface Response : NSObject
#property (strong, nonatomic) NSArray *data;
#property (assign, nonatomic) BOOL success;
#end
#interface SomeAPIInfo : NSObject
#property (strong, nonatomic) NSString *name;
#property (strong, nonatomic) NSString *path;
#property (strong, nonatomic) NSString *minVersion;
#property (strong, nonatomic) NSString *maxVersion;
#end
And here is my mapping settings:
RKObjectMapping *responseMapping = [RKObjectMapping mappingForClass:[Response class]];
[responseMapping addAttributeMappingsFromDictionary:#{#"success": #"success"}];
RKObjectMapping *dataObjectMapping = [RKObjectMapping mappingForClass:[SomeAPIInfo class]];
dataObjectMapping.forceCollectionMapping = YES;
[dataObjectMapping addAttributeMappingFromKeyOfRepresentationToAttribute:#"name"];
[dataObjectMapping addAttributeMappingsFromDictionary:#{
#"(name).path": #"path",
#"(name).minVersion": #"minVersion",
#"(name).maxVersion": #"maxVersion"}];
[responseMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"data"
toKeyPath:#"data"
withMapping:dataObjectMapping]];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:responseMapping
pathPattern:nil
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[_objectManager addResponseDescriptor:responseDescriptor];
Problem is that "data" object is not properly mapped:
NSArray *data;
is filled with 2 "SomeAPIInfo" objects. name is properly filled, but other values(path,maxVersion,minVersion) are empty.
What am I doing wrong ?
Is there another way to map "data" object ? Maybe directly into NSDictionary, so "Some.API.Auth" would be key and "SomeAPIInfo" would be object (without "name" property).
Thanks for help!
Edit: I think that the mapping doesn't work because of the dots in key ("SOME.API.Auth).
RKMappingOperation.m:
- (NSArray *)simpleAttributeMappings
{
NSMutableArray *mappings = [NSMutableArray array];
for (RKAttributeMapping *mapping in self.nestedAttributeMappings) {
if ([mapping.sourceKeyPath rangeOfString:#"."].location == NSNotFound) {
[mappings addObject:mapping];
}
}
I ran into similar issues that was caused by a bug in RestKit https://github.com/RestKit/RestKit/issues/1532 . I'd try to eliminate the possible dot issue by creating a fake JSON response with the same structure but without the dots and see if the problem is still there.

Restkit object mappings not working

I am trying to learn restkit and right now and working on object mappings. Here is the JSON that I am trying to receive and process (generated by ASP.NET MVC Web API):
[
{
"Id": 0,
"Title": "Test",
"Location": "Test",
"Score": {
"Hill": 10,
"LVille": 12
},
"TimeStart": "2012-11-10T12:30:00",
"TimeEnd": "2012-11-10T13:20:00",
"Gender": 0,
"State": 0
},
{
"Id": 0,
"Title": "ee",
"Location": "12:00",
"Score": {
"Hill": 13,
"LVille": 0
},
"TimeStart": "12:00 PM",
"TimeEnd": "12:00 PM",
"Gender": 1,
"State": 1
}
]
The objects that are being mapped to are (SBGame):
#interface SBGame : NSObject
#property (nonatomic, copy) NSNumber* Id;
#property (nonatomic, copy) NSString* Title;
#property (nonatomic, copy) NSString* Location;
#property (nonatomic, strong) SBScore* Score;
#property (nonatomic, copy) NSString* TimeStart;
#property (nonatomic, copy) NSString* TimeEnd;
#property (nonatomic, copy) NSNumber* Gender;
#property (nonatomic, copy) NSNumber* State;
#end
and SBScore :
#interface SBScore : NSObject
#property (nonatomic, copy) NSNumber* Hill;
#property (nonatomic, copy) NSNumber* LVille;
#end
and last but not least the object mappings
RKObjectMapping *ScoreMapping = [RKObjectMapping mappingForClass:[SBScore class]];
[ScoreMapping addAttributeMappingsFromArray:#[#"Hill", #"LVille"]];
RKObjectMapping *GameMapping = [RKObjectMapping mappingForClass:[SBGame class]];
[GameMapping addAttributeMappingsFromDictionary:#{
#"Id" : #"Id",
#"Title" : #"Title",
#"Location" : #"Location",
#"TimeStart" : #"TimeStart",
#"TimeEnd" : #"TimeEnd",
#"Gender" : #"Gender",
#"State" : #"State",
}];
[GameMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"score" toKeyPath:#"score" withMapping:ScoreMapping]];
RKResponseDescriptor *decp = [RKResponseDescriptor responseDescriptorWithMapping:GameMapping pathPattern:#"/Games" keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptor:decp];
after all that the error that I recieve is
2012-12-29 16:49:19.553 Scoreboard[3004:c07] Failure with error Error Domain=org.restkit.RestKit.ErrorDomain Code=1001
"Unable to find any mappings for the given content" UserInfo=0x75c1460 {DetailedErrors=(
), NSLocalizedDescription=Unable to find any mappings for the given content, keyPath=null}
I am very new to RestKit and I have looked through The Object Mapping Tutorial but still it does not work. Any ideas are much appreciated.
I figured it out. When I created the default instance I made the base URL www.example.com/api and then just made the binding and the getObjects method to /Games
What I should have done is made the base URL www.example.com set the path pattern to /api/Games and call the getObjects method on /api/games
So from the code shown
RKResponseDescriptor *decp
= [RKResponseDescriptor responseDescriptorWithMapping:GameMapping
pathPattern:#"/Games"
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
changes to
RKResponseDescriptor *decp
= [RKResponseDescriptor responseDescriptorWithMapping:GameMapping
pathPattern:#"/api/Games"
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];

Restkit: Post Json Array and Map response to Managed Objects

Here is my setup, Post a list of objects in Json to server and got back with updated results that will be mapped to core data and updated.
-- Post Boday --
{
"memberId": "1000000",
"contacts": [
{
"phoneNumber": "+12233333333",
"firstName": "john",
"lastName": "H"
},
{
"phoneNumber": "+12244444444",
"firstName": "mary",
"lastName": "K"
}
]
}
-- Post Response --
{
"contacts": [
{
"phoneNumber": "+12233333333",
"firstName": "john",
"lastName": "k",
"isMember": "yes"
},
{
"phoneNumber": "+12244444444",
"firstName": "mary",
"lastName": "k",
"isMember": "no"
}
]
}
I found another thread which discusses very similar to my case. RestKit: How does one post an array of objects?
This is my setup.
-- SHContact.h --
#interface SHContact : NSManagedObject {}
#property (nonatomic, strong) NSString* phoneNumber;
#property (nonatomic, strong) NSString* firstName;
#property (nonatomic, strong) NSString* lastName;
#property (nonatomic, strong) NSNumber* isMember;
#end
-- SHContactPost.m --
#import "SHContactPost.h"
#implementation SHContactPost
#synthesize contacts;
#synthesize memberId;
- (NSArray*)contacts {
return <list-of-SHContact>;
}
- (NSString *)memberId {
return #"my-member-id";
}
#end
-- RK Mapping --
// Setup our object mappings for SHContact
RKManagedObjectMapping* contactMapping =
[RKManagedObjectMapping mappingForClass:[SHContact class]
inManagedObjectStore:objectManager.objectStore];
contactMapping.primaryKeyAttribute = #"phoneNumber";
[contactMapping mapAttributes:#"phoneNumber",
#"firstName", #"lastName", #"isMember", nil];
[objectManager.mappingProvider setObjectMapping:contactMapping
forKeyPath:#"contacts"];
[[objectManager mappingProvider]
setSerializationMapping:[contactMapping inverseMapping]
forClass:[SHContact class]];
RKObjectMapping *cPostMapping = [RKObjectMapping
mappingForClass:[SHContactPost class]];
[cPostMapping mapKeyPath:#"contacts"
toRelationship:#"contacts"
withMapping:contactMapping];
[cPostMapping mapAttributes:#"memberId", nil];
[objectManager.mappingProvider
setSerializationMapping:[cPostMapping inverseMapping]
forClass:[SHContactPost class]];
[objectManager.router routeClass:[SHContactPost class]
toResourcePath:#"/contacts"
forMethod:RKRequestMethodPOST];
objectManager.serializationMIMEType = RKMIMETypeJSON;
-- Post to server --
SHContactPost *post = [[SHContactPost alloc] init];
RKObjectManager* manager = [RKObjectManager sharedManager];
[manager postObject:post delegate:self];
Server post and response is SUCCESSFUL. However, Reskit was not able to map the result back to the list of SHContact objects. This is the exception. Am I missing something?
restkit.network:RKObjectLoader.m:222 Encountered errors during mapping:
Cannot map a collection of objects onto a non-mutable collection.
Unexpected destination object type 'SHContactPost'
UPDATE: This is what I changed to make it work for me.
-- RK Mapping --
// Setup our object mappings for SHContact
RKManagedObjectMapping* contactMapping =
[RKManagedObjectMapping mappingForClass:[SHContact class]
inManagedObjectStore:objectManager.objectStore];
contactMapping.primaryKeyAttribute = #"phoneNumber";
[contactMapping mapAttributes:#"phoneNumber",
#"firstName", #"lastName", #"isMember", nil];
[objectManager.mappingProvider addObjectMapping:contactMapping];
-- Post to server --
SHContactPost *post = [[SHContactPost alloc] init];
RKObjectManager* manager = [RKObjectManager sharedManager];
RKObjectMapping* responseMapping = [manager.mappingProvider
objectMappingForClass:[SHContact class]];
[manager postObject:post usingBlock:^(RKObjectLoader *loader) {
loader.delegate = self;
loader.targetObject = nil;
responseMapping.rootKeyPath = #"contacts";
loader.objectMapping = responseMapping;
}];
loader.targetObject needs to set it to nil, otherwise, it won't work.