RestKit 0.20: flattening the hierarchy with dynamic in JSON - ios

I am using RestKit 0.20 and have a problem with mapping a JSON with hierarchy which contains dynamic keys into one Object.
The JSON looks like this:
{
"id": 42,
"name": "Name of this entity",
"specialDataMap": {
"2091:10": {
"id": 2091,
"type": "10",
"value": "1'509.49",
"name": "Name of special data type 10"
},
"2091:02" {
"id": 2091,
"type": "02",
"value": "5.5543",
"name": "Name of special data type 02"
}
}
}
and should be mapped with RestKit to such an object:
#interface InfoPoint : NSManagedObject
#property (nonatomic, retrain) NSString* identifier;
#property (nonatomic, retrain) NSString* name;
#property (nonatomic, retrain) NSString* valueOfType10;
#property (nonatomic, retrain) NSString* valueOfType02;
#end
As you can see, I do not want to create a relationship and store the special data into a separate object. It just doesn't make sense.
I want to assign the nested attributes into the InfoPoint object like all other attributes. Usually this would work with the key path of the nested objects but this path contains a dynamic part: "2091:10" is a combination of the id and the type where the id might change (was not my 'original' idea but I have to consume it).
I have read about the Handling Dynamic Nesting Attributes in the RestKit documentation. But I did not find if and how this might work together with nested attributes.
----- added as response to comment/question: ----
I have tried it as well with Dynamic Object Mapping. But it did not work because RestKit seems to have problem with the #"self" destination:
RKEntityMapping* type10Mapping = [RKEntityMapping mappingForEntityForName:#"InfoPoint" inManagedObjectStore:objectStore];
[type10Mapping addAttributeMappingsFromDictionary:#{
#"value": #"valueOfType10"}];
RKDynamicMapping* dynamicMapping = [[RKDynamicMapping alloc] init];
[mapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"specialDataMap" toKeyPath:#"self" withMapping:dynamicMapping]];
[dynamicMapping setObjectMappingForRepresentationBlock:^RKObjectMapping *(id representation) {
if ([[representation valueForKeyPath:#"type"] isEqualToString:#"10"]) {
return type10Mapping;
}
return nil;
}];

Your attempt at dynamic mapping isn't correct. You should supply the dynamic mapping to the response descriptor. The dynamic mapping should then create and return the appropriate mapping based on the keys it finds in the response. Something like (written free hand):
RKDynamicMapping* dynamicMapping = [[RKDynamicMapping alloc] init];
[dynamicMapping setObjectMappingForRepresentationBlock:^RKObjectMapping *(id representation) {
RKEntityMapping* typeMapping = [RKEntityMapping mappingForEntityForName:#"InfoPoint" inManagedObjectStore:objectStore];
[typeMapping addAttributeMappingsFromDictionary:#{
#"id" : #"identifier",
#"name": #"name",}];
NSDictionary *types = [representation valueForKeyPath:#"specialDataMap"];
for (NSString *key in types) {
NSDictionary *type = [[types objectForKey:key] objectForKey:#"type"];
if ([type isEqualToString:#"10"]) {
[typeMapping addAttributeMappingsFromDictionary:#{
[NSString stringWithFormat:#"specialDataMap.%#.value", key]: #"valueOfType10"}];
} else if ...
}
return typeMapping;
}];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:dynamicMapping
pathPattern:...
keyPath:nil
statusCodes:[NSIndexSet indexSetWithIndex:200]];

Related

Mapping top level items and embedded arrays with RestKit

Given the following JSON structure:
{
"foo": {
"anno": "blah",
"domini": null,
"locations": [
{
"data": {
"lat": null,
"lon": null
},
"data": {
"lat": null,
"lon": null
}
}
]
}
}
How do I set up RestKit mappings for this scenario? I though I had it, but I'm unable to map the top-level foo items anno, and domini. I can successfully map locations on its own, but not in coordination with foo.
I've done this successfully in the past, but something is escaping me now.
Foo.h
#interface Foo : NSObject
#property (nonatomic, strong) NSString *anno;
#property (nonatomic, strong) NSString *domini;
#end
Location.h
#interface LocationData : NSObject
#property NSString *lat;
#property NSString *lon;
#end
Controller.m
RKObjectMapping *fooMapping = [RKObjectMapping mappingForClass:[Foo class]];
[fooMapping addAttributeMappingsFromArray:#[#"anno", #"domini"]];
RKObjectMapping *locationMapping = [RKObjectMapping mappingForClass:[Location class]];
[locationMapping addAttributeMappingsFromArray:#[#"lat",#"lon"]];
[fooMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"location" toKeyPath:#"location" withMapping: locationMapping]];
RKResponseDescriptor *fooReponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:dataMapping method:RKRequestMethodGET pathPattern:#"foo" keyPath:#"foo" statusCodes:[NSIndexSet indexSetWithIndex:200]];
RKResponseDescriptor *locationResponseDescriptor =
[RKResponseDescriptor responseDescriptorWithMapping:locationdMapping
method:RKRequestMethodGET
pathPattern:nil
keyPath:#"foo.location"
statusCodes:[NSIndexSet indexSetWithIndex:200]];
I think that's all of the important stuff. Hopefully in my zeal to pare down how much text I was posting I didn't leave anything important out.
EDIT 2015-03-29
- (void)loadChildren {
NSDictionary *queryParams = #{#"sort" : #"new"};
[[RKObjectManager sharedManager] getObjectsAtPath:redditPath
parameters:queryParams
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
_children = mappingResult.array;
[self.tableView reloadData];
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"You mean YOU'RE the lunatic who's responsible for almost destroying my ship? : %#", error);
}];
}
redditpath is set earlier using...
redditPath = [NSString stringWithFormat:#"/r/%#/new.json", subRedditToLoad];
Where subRedditToLoad is, in this case, aww.
In your XCDataModel Take an Entity with named Foo.
Set Property of anno and vomini.
Also Create another Entity Location.
Set a property named data with type Transformable.
Add Relationship at Foo for Location entity put it many relationship.
Call mapping for Foo Like:
[mapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"foo" toKeyPath:#"Foo" withMapping:[Location objectMappingForLocation:Enum]]];

RestKit using RKConnectionDescription to sideload data [duplicate]

I'm totally new to RestKit and am struggling somewhat.
JSON:
{
"teams": [
{
"id": 1,
"name": "Team A"
},
{
"id": 2,
"name": "Team B"
}
],
"users": [
{
"id": 1,
"name": "cameron",
"teamId": 1
},
{
"id": 2,
"name": "paul",
"teamId": 2
}
]
}
CoreData:
#interface Team : NSManagedObject
#property (nonatomic, retain) NSNumber * teamId;
#property (nonatomic, retain) NSString * name;
#end
#interface User : NSManagedObject
#property (nonatomic, retain) NSNumber * userId;
#property (nonatomic, retain) NSString * name;
#property (nonatomic, retain) Team * team;
#end
My application logic looks like this:
// team mapping
RKEntityMapping *teamMapping = [RKEntityMapping mappingForEntityForName:#"Team" inManagedObjectStore:[RKManagedObjectStore defaultStore]];
teamMapping.identificationAttributes = #[#"teamId"];
[teamMapping addAttributeMappingsFromDictionary:#{
#"id": #"teamId",
#"name": #"name"
}];
// Register our mappings with the provider
RKResponseDescriptor *teamResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:teamMapping
pathPattern:nil
keyPath:#"teams"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[self.objectManager addResponseDescriptor:teamResponseDescriptor];
// user mapping
RKEntityMapping *userMapping = [RKEntityMapping mappingForEntityForName:#"User" inManagedObjectStore:[RKManagedObjectStore defaultStore]];
userMapping.identificationAttributes = #[#"userId"];
[userMapping addAttributeMappingsFromDictionary:#{
#"id": #"userId",
#"name": #"name"
}];
// Register our mappings with the provider
RKResponseDescriptor *userResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:userMapping
pathPattern:nil
keyPath:#"users"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[self.objectManager addResponseDescriptor:userResponseDescriptor];
I can't for the life of me work out how to get RestKit to populate the team Property of the user objects.
I've look at so many posts but nothing I try works, is this not a usual use case?
Does anyone know how to do this, help would be very appreciated!
Thanks.
You need to add a transient attribute to your User entity which holds the associated teamId. This needs to be added to your userMapping.
Then, you need to add a relationship definition to your userMapping:
[userMapping addConnectionForRelationship:#"team" connectedBy:#{ #"teamId": #"teamId" }];
This gives RestKit the information it needs to make the connection and instructs it to make the connection as part of the mapping operation.

iOS Restkit: how to parse nested path

I had a JSON response of this type:
{
"d": { "id": "1", "user": "test"}
}
which I was parsing with Restkit with the following code:
#interface ODataUser : NSObject<ODataObject>
#property (nonatomic, copy) NSString * id;
#property (nonatomic, copy) NSString * user;
-(NSString*)getId;
-(NSString*)getUser;
#end
RKObjectMapping *map = [RKObjectMapping mappingForClass:[ODataUser
class]]; [mapping addAttributeMappingsFromDictionary: #{ #"id" :
#"id", #"user" : #"user" } ];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:map method:RKRequestMethodGET pathPattern:nil keyPath:#"d" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
However, now my response has changed to something like this:
{
"d": { "results": [ {"id": "1", "user": "test"} ] }
}
How can I reflect those changes on the response on my code?
Change your response descriptor to use:
keyPath:#"d.results"
as this will navigate into the d dictionary to get the results array and process all of the dictionaries it contains.

Foreign key relationship mapping with RestKit

I'm totally new to RestKit and am struggling somewhat.
JSON:
{
"teams": [
{
"id": 1,
"name": "Team A"
},
{
"id": 2,
"name": "Team B"
}
],
"users": [
{
"id": 1,
"name": "cameron",
"teamId": 1
},
{
"id": 2,
"name": "paul",
"teamId": 2
}
]
}
CoreData:
#interface Team : NSManagedObject
#property (nonatomic, retain) NSNumber * teamId;
#property (nonatomic, retain) NSString * name;
#end
#interface User : NSManagedObject
#property (nonatomic, retain) NSNumber * userId;
#property (nonatomic, retain) NSString * name;
#property (nonatomic, retain) Team * team;
#end
My application logic looks like this:
// team mapping
RKEntityMapping *teamMapping = [RKEntityMapping mappingForEntityForName:#"Team" inManagedObjectStore:[RKManagedObjectStore defaultStore]];
teamMapping.identificationAttributes = #[#"teamId"];
[teamMapping addAttributeMappingsFromDictionary:#{
#"id": #"teamId",
#"name": #"name"
}];
// Register our mappings with the provider
RKResponseDescriptor *teamResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:teamMapping
pathPattern:nil
keyPath:#"teams"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[self.objectManager addResponseDescriptor:teamResponseDescriptor];
// user mapping
RKEntityMapping *userMapping = [RKEntityMapping mappingForEntityForName:#"User" inManagedObjectStore:[RKManagedObjectStore defaultStore]];
userMapping.identificationAttributes = #[#"userId"];
[userMapping addAttributeMappingsFromDictionary:#{
#"id": #"userId",
#"name": #"name"
}];
// Register our mappings with the provider
RKResponseDescriptor *userResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:userMapping
pathPattern:nil
keyPath:#"users"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[self.objectManager addResponseDescriptor:userResponseDescriptor];
I can't for the life of me work out how to get RestKit to populate the team Property of the user objects.
I've look at so many posts but nothing I try works, is this not a usual use case?
Does anyone know how to do this, help would be very appreciated!
Thanks.
You need to add a transient attribute to your User entity which holds the associated teamId. This needs to be added to your userMapping.
Then, you need to add a relationship definition to your userMapping:
[userMapping addConnectionForRelationship:#"team" connectedBy:#{ #"teamId": #"teamId" }];
This gives RestKit the information it needs to make the connection and instructs it to make the connection as part of the mapping operation.

Map JSON "Associative array" into CoreData with RestKit

I need to map JSON associative array of objects with RestKit(iOS).
It looks like object with properties 135,145,423 and objects on it.
{
"135": {
"name" : "Object1",
"property1" : "Value1",
"anotherProperty1" : "Value2"
},
"145": {
"name": "Object2",
"property1" : "Value1",
"anotherProperty1" : "Value2"
},
"423": {
"name": "Object3",
"property1" : "Value1",
"anotherProperty1" : "Value2"
}
}
I've got mapping for single object that works.
Mapping performs to CoreData.
The only solution i have is to convert associative array to ordinary array and place number to "id" field, but i don't think it's elegant solution.
Is there any right way to perform such mapping directly with RestKit?
Here's the solution for my situation.
NSIndexSet *statusCodes = RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful); // Anything in 2xx
// 1. Create dynamic mapping
RKDynamicMapping* dynamicMapping = [[RKDynamicMapping alloc] init];
// 2. Process every entry separately
dynamicMapping.forceCollectionMapping = YES;
// 3. Set mappings for every object
[dynamicMapping setObjectMappingForRepresentationBlock:^RKObjectMapping *(id representation) {
// 4. Mapping to Core Data (Can be replaced with RKObjectMapping if there's no need of CodeData)
RKEntityMapping *singleRouteMapping = [RKEntityMapping mappingForEntityForName:#"Object" inManagedObjectStore:managedObjectStore];
// 5. Walking through all keys (but with dynamicMapping.forceCollectionMapping = YES) there'll be only one. It's better to refactor it.
for (NSString *key in representation) {
// 6. Set mappings for every property exect 'id'
[singleRouteMapping addAttributeMappingsFromDictionary:#{
[NSString stringWithFormat: #"%#.name", key]: #"name",
[NSString stringWithFormat: #"%#.property1", key]: #"property1",
[NSString stringWithFormat: #"%#.anotherProperty1", key]: #"anotherProperty1"
}];
}
// 7. Map 'id' property at last
[singleRouteMapping addAttributeMappingFromKeyOfRepresentationToAttribute: #"id"];
return singleRouteMapping;
}];
RKResponseDescriptor *pluralDescriptor = [RKResponseDescriptor responseDescriptorWithMapping: dynamicMapping
pathPattern: #"/api/objects"
keyPath: nil
statusCodes: statusCodes];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.somesite.com/api/objects"]];
RKManagedObjectRequestOperation *operation = [[RKManagedObjectRequestOperation alloc] initWithRequest:request responseDescriptors:#[pluralDescriptor]];
You need to use a dynamic mapping, where the mapping is created specifically for the received keys in the dictionary. You don't say what the destination object is or what your mappings are so this is a general example (for Core Data, but can be changed to plain objects):
RKDynamicMapping* dynamicMapping = [[RKDynamicMapping alloc] init];
[dynamicMapping setObjectMappingForRepresentationBlock:^RKObjectMapping *(id representation) {
RKEntityMapping* typeMapping = [RKEntityMapping mappingForEntityForName:#"..." inManagedObjectStore:objectStore];
for (NSString *key in representation) {
NSDictionary *type = [representation objectForKey:key];
[typeMapping addAttributeMappingsFromDictionary:#{
[NSString stringWithFormat:#"%#.name", key]: #"name"}];
}
return typeMapping;
}];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:dynamicMapping
pathPattern:...
keyPath:nil
statusCodes:[NSIndexSet indexSetWithIndex:200]];
This basically strips out the numbers and throws them away. You could include them if required by configuring the dynamic mapping.

Resources