Good afternoon, everyone!
I'm trying to figure out how to map objects with nested arrays, but my project keeps being terminated due to an uncaught exception. I assume I'm not mapping something correctly, but I'm being told something isn't key-value coding compliant.
How do I map an object with a nested array?
Following is the footprint of the JSON I'm trying to map, the interface and implementation, and the error that's being throw, respectively. Finally, there is a link to my project on GitHub, incase I've left anything out, or seeing the full source would be helpful.
JSON
{
"href": "string",
"items": [
{
"type": "string",
"status": "string",
"name": "string",
"publisher": "string",
"publisherId": "string",
"description": "string",
"url": "string",
"smallLogoImageUrl": "string",
"tileImageUrl": "string",
"heroImageUrl": "string",
"tags": [
"string",
"string"
],
"createdOn": "2015-04-22T18:55:40.782Z",
"downloadUrl": "string",
"getProductCodeUrl": "string",
"metadata": {
"exeType": "string",
"packageFileName": "string",
"installDirectory": "string",
"executableName": "string"
},
"id": "string"
}
]
}
Interface (.h)
#interface SFrontPageItem : NSObject
#property (nonatomic, strong) NSString *type;
#property (nonatomic, strong) NSString *status;
#property (nonatomic, strong) NSString *name;
#property (nonatomic, strong) NSString *publisher;
#property (nonatomic, strong) NSString *publisherId;
#property (nonatomic, strong) NSString *productDescription;
#property (nonatomic, strong) NSString *url;
#property (nonatomic, strong) NSString *smallLogoImageUrl;
#property (nonatomic, strong) NSString *tileImageUrl;
#property (nonatomic, strong) NSString *heroImageUrl;
#property (nonatomic, strong) NSArray *tags;
#property (nonatomic, strong) NSString *createdOn;
#property (nonatomic, strong) NSString *downloadUrl;
#property (nonatomic, strong) NSString *getProductCodeUrl;
#property (nonatomic, strong) NSArray *metadata;
#property (nonatomic, strong) NSString *productID;
#end
#interface SFrontPage : NSObject
#property (nonatomic, strong) NSString *href;
#property (nonatomic, strong) NSArray *items;
#end
Implementation (.m)
- (void) getFrontPage
{
AppDelegate *appDelegate = [[NSApplication sharedApplication] delegate];
RKObjectMapping *itemMapping = [RKObjectMapping mappingForClass:[SFrontPageItem class]];
[itemMapping addAttributeMappingsFromDictionary:#{
#"type": #"type",
#"status": #"status",
#"name": #"name",
#"publisher": #"publisher",
//#"publisherId": #"publisherId",
#"description": #"description",
#"url": #"url",
//#"smallLogoImageUrl": #"smallLogoImageUrl",
#"tileImageUrl": #"tileImageUrl",
//#"heroImageUrl": #"heroImageUrl",
//#"tags": #"tags",
#"createdOn": #"createdOn",
//#"downloadUrl": #"downloadUrl",
//#"getProductCodeUrl": #"getProductCodeUrl",
//#"metadata": #"metadata",
#"id": #"productID"
}];
//itemMapping.forceCollectionMapping = YES;
RKObjectMapping *frontpageMapping = [RKObjectMapping mappingForClass:[SFrontPage class]];
[frontpageMapping addAttributeMappingsFromDictionary:#{
#"href": #"href"
}];
[frontpageMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"items"
toKeyPath:#"items"
withMapping:itemMapping]];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:frontpageMapping
method:RKRequestMethodGET
pathPattern:nil
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[self.objectManager.HTTPClient setAuthorizationHeaderWithUsername:self.sconnection.apiKey.key password:self.sconnection.apiKey.secret];
[self.objectManager addResponseDescriptor:responseDescriptor];
[self.objectManager getObjectsAtPath:#"/api/frontpage/rest" parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *result)
{
SFrontPage *newFrontpage = result.firstObject;
NSLog (#" HREF: %#", newFrontpage.href);
//NSLog (#"ITEMS: %#", newFrontpage.items.firstObject);
//SFrontPageItem *newFrontpageItem = newFrontpage.items.firstObject;
//NSLog (#"Unexpected Great Thing %#", newFrontpageItem );
[appDelegate.loginViewController apiConnectionSuccess];
} failure:^(RKObjectRequestOperation *operation, NSError *error)
{
[appDelegate.loginViewController updateLoginWindowHeaderLabelTo:#"Unable to Load Frontpage"];
[appDelegate.loginViewController apiConnectionFailure];
}];
}
Error
[<SFrontPageItem 0x6180001026d0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key description.
Source
The full source can be found on GitHub, with the relevant files being APIManager.h & APIManager.m.
I hope I've been clear enough, I sometimes miss the mark when forming a question about something I don't completely understand. I'm new to both ObjC, and RestKit, so I'm sure there are already a lot of confusing things in my code. Thanks for taking the time to read through it, and consider my question. If I can clarify anything, please let me know!
Michael
You map from description to description, but the property has the identifier productDescription. Change the mapping or the property name.
Try use some ready solutions.
https://github.com/aryaxt/OCMapper
https://github.com/isair/JSONHelper
Related
I am fetching data from the New York Times Bestsellers JSON API using Reskit. I believe I have an issue with my attributes mapping. A typical JSON object that has to be fetched looks like the structure below. My code is also shown. The API call does return objects as matching the number of expected results but the RKMappingResult in the requestDataFromAPI method returns "[__NSCFBoolean length]: unrecognized selector sent to instance". I am not able to access them as printing Books gives nil. I am not sure what I'm doing wrong.
{
"status":"OK",
"copyright":"Copyright (c) 2016 The New York Times Company. All Rights Reserved.",
"num_results":10,
"last_modified":"2016-03-04T13:12:31-05:00",
"results":
{
"list_name":"Animals",
"bestsellers_date":"2016-02-27",
"published_date":"2016-03-13",
"display_name":"Animals",
"normal_list_ends_at":10,
"updated":"MONTHLY",
"books": [
{"rank":1,
"rank_last_week":0,
"weeks_on_list":0,
"asterisk":0,
"dagger":0,
"primary_isbn10":"0802123414",
"primary_isbn13":"9780802123411",
"publisher":"Grove Atlantic",
"description":"A grief-stricken British woman decides to raise a goshawk, a fierce bird that is notoriously difficult to tame.",
"price":0,
"title":"H IS FOR HAWK","author":"Helen Macdonald",
"contributor":"by Helen Macdonald",
"isbns": [
{"isbn10":"0802123414",
"isbn13":"9780802123411"
},
{"isbn10":"1448130727",
"isbn13":"9781448130726"
},
{"isbn10":"1481530968",
"isbn13":"9781481530965"
},
{"isbn10":"148153095X",
"isbn13":"9781481530958"
},
{"isbn10":"1410483614",
"isbn13":"9781410483614"
},
{"isbn10":"0802124739",
"isbn13":"9780802124739"
}]
}
}
- (void) initializeRestAPI
{
// Initialize RestKit using API base address
NSURL * baseURL = [NSURL URLWithString:#"http://api.nytimes.com"];
RKObjectManager * objectManager = [RKObjectManager managerWithBaseURL:baseURL];
// Initialize Core Data's managed object model from the bundle
NSManagedObjectModel * managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
// Initialize RestKit's managed object store
RKManagedObjectStore * managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
objectManager.managedObjectStore = managedObjectStore;
// Complete Core Data stack initialization via RestKit
[managedObjectStore createPersistentStoreCoordinator];
NSString * persistentStorePath = [RKApplicationDataDirectory() stringByAppendingPathComponent:#"DataModel.sqlite"];
NSString * seedDatabasePath = [[NSBundle mainBundle] pathForResource:#"RKSeedDatabase" ofType:#"sqlite"];
NSError * error;
NSPersistentStore * persistentStore = [managedObjectStore addSQLitePersistentStoreAtPath:persistentStorePath fromSeedDatabaseAtPath:seedDatabasePath withConfiguration:nil options:nil error:&error];
NSAssert(persistentStore, #"Failed to add persistent store with error: %#", error);
// Create RestKit's managed object contexts
[managedObjectStore createManagedObjectContexts];
// Configure a managed object cache
managedObjectStore.managedObjectCache = [[RKInMemoryManagedObjectCache alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext];
[self setupEntityMappingForObjectStore:managedObjectStore withObjectManager:objectManager];
[self requestDataFromAPI];
}
-(void) setupEntityMappingForObjectStore: (RKManagedObjectStore *) managedObjectStore withObjectManager: (RKObjectManager *) objectManager
{
RKEntityMapping * bookListMapping = [RKEntityMapping mappingForEntityForName:#"BookList" inManagedObjectStore:managedObjectStore];
bookListMapping.identificationAttributes = #[#"listName"];
[bookListMapping addAttributeMappingsFromDictionary:
#{#"results.list_name": #"listName",
#"results.bestsellers_date": #"bestsellersDate",
#"results.published_date": #"publishedDate",
#"results.display_name": #"displayName",
#"results.normal_list_ends_at": #"normalListEndsAt",
#"results.updated": #"updated"
}];
RKEntityMapping * bookMapping = [RKEntityMapping mappingForEntityForName:#"Book" inManagedObjectStore:managedObjectStore];
bookMapping.identificationAttributes = #[#"title"];
[bookMapping addAttributeMappingsFromDictionary:
#{#"rank": #"rank",
#"rank_last_week": #"rankLastWeek",
#"weeks_on_list": #"weeksOnList",
#"primary_isbn10": #"primaryIsbn10",
#"primary_isbn13": #"primaryIsbn13",
#"amazon_product_url": #"productUrl",
#"book_image": #"bookImage",
#"publisher": #"publisher",
#"description": #"bookDescription",
#"title": #"title",
#"contributor": #"contributor",
#"author": #"author",
#"price": #"price"
}];
[bookListMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"results.books" toKeyPath:#"books" withMapping:bookMapping]];
RKResponseDescriptor * bookListResponseDescriptor =
[RKResponseDescriptor responseDescriptorWithMapping:bookListMapping
method:RKRequestMethodGET
pathPattern:nil
keyPath:#"results.books"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)
];
[objectManager addResponseDescriptor:bookListResponseDescriptor];
// Enable Activity Indicator Spinner
[AFNetworkActivityIndicatorManager sharedManager].enabled = YES;
}
- (void)fetchBooksFromContext
{
NSManagedObjectContext * context = [RKManagedObjectStore defaultStore].mainQueueManagedObjectContext;
NSFetchRequest * fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"BookList"];
NSSortDescriptor * descriptor = [NSSortDescriptor sortDescriptorWithKey:#"listName" ascending:YES];
fetchRequest.sortDescriptors = #[descriptor];
NSError *error = nil;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
BookList * bookList = [fetchedObjects firstObject];
NSArray * books = [bookList.books allObjects];
//NSArray * books = [fetchedObjects firstObject];
NSLog(#"Books: %#",books);
}
- (void)requestDataFromAPI
{
NSDictionary * apiKeyData = [[NSUserDefaults standardUserDefaults] objectForKey:#"apiKeyData"];
NSString * apiKey = [apiKeyData objectForKey:#"apiKeyData"];
NSLog(#"requestDataFromAPI apiKey: %#",apiKey);
NSString * requestPath = [[NSString alloc] initWithFormat:#"/svc/books/v3/lists/%#?&api-key=%#",_categoryListName, apiKey];
[[RKObjectManager sharedManager]
getObjectsAtPath:requestPath
parameters:nil
success: ^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult)
{
[self fetchBooksFromContext];
}
failure: ^(RKObjectRequestOperation *operation, NSError *error)
{
RKLogError(#"Loading from API failed with error: %#", error);
}
];
}
The core data object models are as shown
#import "BookList.h"
NS_ASSUME_NONNULL_BEGIN
#interface BookList (CoreDataProperties)
#property (nullable, nonatomic, retain) NSString *listName;
#property (nullable, nonatomic, retain) NSDate *bestsellersDate;
#property (nullable, nonatomic, retain) NSDate *publishedDate;
#property (nullable, nonatomic, retain) NSString *displayName;
#property (nullable, nonatomic, retain) NSNumber *normalListEndsAt;
#property (nullable, nonatomic, retain) NSString *updated;
#property (nullable, nonatomic, retain) NSSet<Book *> *books;
#end
#interface BookList (CoreDataGeneratedAccessors)
- (void)addBooksObject:(Book *)value;
- (void)removeBooksObject:(Book *)value;
- (void)addBooks:(NSSet<Book *> *)values;
- (void)removeBooks:(NSSet<Book *> *)values;
#end
NS_ASSUME_NONNULL_END
#import "Book.h"
NS_ASSUME_NONNULL_BEGIN
#interface Book (CoreDataProperties)
#property (nullable, nonatomic, retain) NSNumber *rankLastWeek;
#property (nullable, nonatomic, retain) NSNumber *weeksOnList;
#property (nullable, nonatomic, retain) NSString *primaryIsbn10;
#property (nullable, nonatomic, retain) NSString *primaryIsbn13;
#property (nullable, nonatomic, retain) NSString *productUrl;
#property (nullable, nonatomic, retain) NSString *bookImageUrl;
#property (nullable, nonatomic, retain) NSString *publisher;
#property (nullable, nonatomic, retain) NSString *bookDescription;
#property (nullable, nonatomic, retain) NSString *title;
#property (nullable, nonatomic, retain) NSString *contributor;
#property (nullable, nonatomic, retain) NSString *author;
#property (nullable, nonatomic, retain) NSNumber *price;
#end
NS_ASSUME_NONNULL_END
In my core data model I have two entities:
InventoryItem
InventoryAction
InventoryItem has the following attributes:
Attributes:
#property (nonatomic, retain) NSNumber * allowsActions;
#property (nonatomic, retain) NSNumber * assetID;
#property (nonatomic, retain) NSNumber * inventoryObjectID;
#property (nonatomic, retain) NSString * objectDescription;
#property (nonatomic, retain) NSNumber * quantity;
#property (nonatomic, retain) NSNumber * retired;
#property (nonatomic, retain) NSString * serialNumber;
Relationships:
#property (nonatomic, retain) NSSet *action;
InventoryAction has the following attributes
Attributes:
#property (nonatomic, retain) NSDate * actionDate;
#property (nonatomic, retain) NSNumber * actionID;
#property (nonatomic, retain) NSString * actionLongValue;
#property (nonatomic, retain) NSString * actionShortValue;
#property (nonatomic, retain) NSString * notes;
#property (nonatomic, retain) NSNumber * userActionID;
#property (nonatomic, retain) NSString * userAuthorizingAction;
#property (nonatomic, retain) NSString * userPerformingAction;
#property (nonatomic, retain) NSNumber * userPerformingActionExt;
#property (nonatomic, retain) NSNumber * inventoryObjectID;
Relationships
#property (nonatomic, retain) InventoryItem *object;
Now that you have all that information I will tell you that all of the values that are being stored in the CoreData model are coming from a web service in JSON format. All of the values are storing properly.
The problem that I'm having is that when I fetch the data from CoreData it only brings back values that are unique.
For instance:
The last InventoryItem has 6 actions associated with it:
"",
"",
"it looks kinda slimy",
"oh god....why would you do that to an inanimate object you sicko",
"its all slimy now",
"everything looks good"
As 1 and 2 have the same value it only retrieves value 1, 3, 4, 5, and 6.
Here is the code I'm using to fetch the data:
- (void)loadDetails
{
_fetchRequest = [[NSFetchRequest alloc] init];
_entity = [NSEntityDescription entityForName:#"InventoryObject" inManagedObjectContext:[self managedObjectContext]];
_sort = [NSSortDescriptor sortDescriptorWithKey:#"inventoryObjectID" ascending:YES];
_sortDescriptors = [[NSArray alloc]initWithObjects:_sort, nil];
[_fetchRequest setEntity:_entity];
[_fetchRequest setSortDescriptors:_sortDescriptors];
NSError *error;
_fetchedObjects = [[self managedObjectContext] executeFetchRequest:_fetchRequest error:&error];
for (InventoryItem *inventoryItem in _fetchedObjects) {
NSLog(#"Object Description: %#", [inventoryItem valueForKey:#"objectDescription"]);
NSLog(#"Object ID: %#", [inventoryItem valueForKey:#"inventoryObjectID"]);
InventoryAction *action = (InventoryAction *)inventoryItem.action;
NSLog(#"Action Long Value: %#", [action valueForKey:#"actionLongValue"]);
NSLog(#"Notes: %#", [action valueForKey:#"notes"]);
}
}
Here is the JSON return:
[{"MediaInventoryObjectsId":1,"AssetId":15,"Quantity":1,"SerialNumber":"R45DFL5","Description":"Test Camera 1","AllowActions":true,"Retired":false,"Actions":[{"MediaInventoryActionsId":3,"MediaInventoryObjectsId":1,"UserPerformingActionExt":4444,"UserActionId":1,"ActionDate":"2014-05-19T15:31:45.6","UserPerformingAction":"myersb","UserAuthorizingAction":"mccroskeyl","Notes":null,"ActionShortValue":"OUT","ActionLongValue":"Check Out"},{"MediaInventoryActionsId":4,"MediaInventoryObjectsId":1,"UserPerformingActionExt":4444,"UserActionId":2,"ActionDate":"2014-05-19T15:31:45.6","UserPerformingAction":"myersb","UserAuthorizingAction":"mccroskeyl","Notes":"everything looks good","ActionShortValue":"IN","ActionLongValue":"Check In"}]},{"MediaInventoryObjectsId":2,"AssetId":15,"Quantity":2,"SerialNumber":"IDKMYBFFJILL","Description":"Vanilla Ice Cream","AllowActions":true,"Retired":false,"Actions":[]},{"MediaInventoryObjectsId":3,"AssetId":15,"Quantity":1,"SerialNumber":"R2D23P0","Description":"Droid Bee Box","AllowActions":true,"Retired":false,"Actions":[{"MediaInventoryActionsId":5,"MediaInventoryObjectsId":3,"UserPerformingActionExt":4444,"UserActionId":1,"ActionDate":"2014-05-20T14:48:29.53","UserPerformingAction":"myersb","UserAuthorizingAction":"mccroskeyl","Notes":null,"ActionShortValue":"OUT","ActionLongValue":"Check Out"},{"MediaInventoryActionsId":6,"MediaInventoryObjectsId":3,"UserPerformingActionExt":4444,"UserActionId":2,"ActionDate":"2014-05-20T14:48:29.58","UserPerformingAction":"myersb","UserAuthorizingAction":"mccroskeyl","Notes":"everything looks good","ActionShortValue":"IN","ActionLongValue":"Check In"},{"MediaInventoryActionsId":7,"MediaInventoryObjectsId":3,"UserPerformingActionExt":5555,"UserActionId":1,"ActionDate":"2014-05-20T14:48:29.6","UserPerformingAction":"farmer","UserAuthorizingAction":"mccroskeyl","Notes":null,"ActionShortValue":"OUT","ActionLongValue":"Check Out"},{"MediaInventoryActionsId":8,"MediaInventoryObjectsId":3,"UserPerformingActionExt":5555,"UserActionId":2,"ActionDate":"2014-05-20T14:48:29.6","UserPerformingAction":"farmer","UserAuthorizingAction":"mccroskeyl","Notes":"its all slimy now","ActionShortValue":"IN","ActionLongValue":"Check In"},{"MediaInventoryActionsId":9,"MediaInventoryObjectsId":3,"UserPerformingActionExt":6666,"UserActionId":1,"ActionDate":"2014-05-20T14:48:29.61","UserPerformingAction":"intern1","UserAuthorizingAction":"mccroskeyl","Notes":"it looks kinda slimy","ActionShortValue":"OUT","ActionLongValue":"Check Out"},{"MediaInventoryActionsId":10,"MediaInventoryObjectsId":3,"UserPerformingActionExt":6666,"UserActionId":2,"ActionDate":"2014-05-20T14:48:29.62","UserPerformingAction":"intern1","UserAuthorizingAction":"mccroskeyl","Notes":"oh god....why would you do that to an inanimate object you sicko","ActionShortValue":"IN","ActionLongValue":"Check In"}]}]
I read somewhere that NSSet possibly only brings back unique records. Is this true and if so what would be the solution as the relationship: action is an NSSet?
Your relationship is declared like this:
#property (nonatomic, retain) NSSet *action;
But you access it like this:
InventoryAction *action = (InventoryAction *)inventoryItem.action;
That's incorrect. The action relationship is an NSSet containing instances of InventoryAction, it's not an InventoryAction itself. Even though you assign this to an InventoryAction *, what you actually have is an NSSet. Then you do this:
NSLog(#"Notes: %#", [action valueForKey:#"notes"]);
If you call valueForKey: on an NSSet, it returns all unique values of that key for objects in the set. In this case it returns all unique values of the notes attribute on the InventoryAction objects in the set, which is what you're seeing. If you want to find every instance instead of every unique value, you need something like:
NSSet *actions = inventoryItem.action;
for (InventoryAction *action in actions) {
NSLog(#"Action note: %#", action.note);
}
[Warning, the above is just typed into my web browser....]
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.
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)];
Suppose I want to send a request to the server with the json
{ "begin_session" : { "info" : "this is some info" } }
and I expect in response the json:
{ "token" : "this is a token", "a_objects" : [
{ "name" : "name of first a_object", "b_objects" : [
{ "name" : "name of first b_object", "type" : "some type value", "id" : "123" },
{ "name" : "name of second b_object", "type" : "some other type value", "id" : "124" }
], "id" : "id of first a_object" },
{ "name" : "name of second a_object", "b_objects" : [
{ "name" : "name of first b_object", "type" : "some type value", "id" : "123" },
{ "name" : "name of third b_object", "type" : "some third type value" , "id" : "125" },
], "id" : "id of second a_object" }
] }
I want to store "token" transiently and persist the a_objects in core data. Is this how I should do the entire process? First, I set up the objects:
#interface LoginToken : NSObject
#property (nonatomic, copy) NSString *token;
#end
#interface AObject : NSManagedObject
#property (nonatomic, retain) NSString *name;
#property (nonatomic, retain) NSSet *bObjects;
#property (nonatomic, retain) NSString *aObjectId;
#end
#implementation AObject
#dynamic name; #dynamic bObjects; #dynamic aObjectId;
#end
#interface BObject : NSManagedObject
#property (nonatomic, retain) NSString *name;
#property (nonatomic, retain) AObject *aObject;
#property (nonatomic, retain) NSString *type;
#property (nonatomic, retain) NSString *bObjectId;
#end
#implementation BObject
#dynamic name; #dynamic aObject; #dynamic type; #dynamic bObjectId;
#end
These are the request parameters:
NSDictionary *params = #{"begin_session":#{#"info":#"this is some info"}};
Then I set up the mappings:
RKObjectMapping *tokenMapping = [RKObjectMapping mappingForClass:[LoginToken class]];
[tokenMapping addAttributeMappingsFromArray:#[#"token"]];
RKResponseDescriptor *tokenResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:tokenMapping pathPattern:nil keyPath:#"token" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
RKEntityMapping *bObjectMapping = [RKEntityMapping mappingForEntityForName:#"BObject" inManagedObjectStore:objectManager.managedObjectStore];
[bObjectMapping addAttributeMappingsFromDictionary:#{#"name":#"name",#"type":#"type", #"id":#"bObjectId"}];
bObjectMapping.identificationAttributes = #[#"bObjectId"];
RKEntityMapping *aObjectMapping = [RKEntityMapping mappingForEntityForName:#"AObject" inManagedObjectStore:objectManager.managedObjectStore];
[aObjectMapping addAttributeMappingsFromDictionary:#{#"name":#"name",#"id":#"aObjectId"}];
[aObjectMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"b_objects" toKeyPath:#"bObjects" withMapping:bObjectMapping]];
aObjectMapping.identificationAttributes = #[#"aObjectId"];
Suppose objectManager is a correctly configured RKObjectManager. I set up the response descriptors:
RKResponseDescriptor *tokenResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:tokenMapping pathPattern:nil keyPath:#"token" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
RKResponseDescriptor *aObjectResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:aObjectMapping pathPattern:nil keyPath:#"a_objects" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptorsFromArray:#[tokenResponseDescriptor, aObjectResponseDescriptor]];
And finally I'll make the request:
[objectManager getObjectsAtPath:#"path" parameters:params success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
LoginToken *token = [mappingResult firstObject]; // use this token transiently
// coredata objects are auto saved
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
// handle error
}];
Is there anything I need to be aware of if this is, in fact, the correct way to do it? Also, how do I set the inverse relationship from BObject to AObject...?
As long as your CoreData file has the relationship configured properly (which it looks correct based on your object header files) then the inverse relationship is handled by the mapper. Otherwise, it should work as is.
Note that the token object will be persisted in CoreData as well so you might have to delete them that's the desired behaviour.