Mapping an array with foreign keys to NSSet of objects

2,346 views
Skip to first unread message

Samuel Sonning

unread,
Apr 24, 2012, 6:09:25 AM4/24/12
to RestKit
Hi

I'm trying to figure out how to configure RestKit object mappings to
automatically map my JSON objects to Core Data.

In this case I have a relationship such as this:

Child >----< Parent

My JSON for Parent looks like:

{
"id": 1,
"children": [
1,
2,
3
],
"name": name
}

and for Child:

{
"id": 1,
"name": name
}

I have found 2 ways of mapping relationship:
1) using elementToRelationshipMappings, but then the object in JSON
(id) doesn't match object in Core Data (NSSet with Child)
2) using a property for key and
relationshipToPrimaryKeyPropertyMappings, but in my case it is to-many
relationship, how can I map this?

Is it possible to automatically map with my JSON data structure ?

Thanks

Samuel

Luke Docksteader

unread,
Dec 5, 2012, 9:46:39 AM12/5/12
to res...@googlegroups.com
I would also like to know the answer to this question. It doest appear that it is possible (according to this post: https://github.com/RestKit/RestKit/issues/284 ) but there is no explanation of how it is to be done. I've tried many things to no avail. Anyone have an answer to this?

Blake Watters

unread,
Dec 5, 2012, 9:12:20 PM12/5/12
to res...@googlegroups.com
You can connect a to-many relationship from an array of ID's using an `RKConnectionDescription` object:

and

What you would do in this case is add a connection for the 'children' relationship connected by @{ @"childrenIDs": @"id" }

Assuming that you have mapped the array of ID's to an attribute named childrenIDs on the Parent and the corresponding attribute on the Child is called id

Luke Docksteader

unread,
Dec 6, 2012, 12:03:12 PM12/6/12
to res...@googlegroups.com
Thanks Blake,

I think I'm getting close, but still running into some errors that I can't seem to get past. In summary:

JSON:
{
"parents": [
{
"id": "1",
"children": [{"id:"1"},{"id":"2"}],
"name": "Bob" 
},
{
"id": "2",
"children": [{"id:"1"}],
"name": "Joan" 
}
],
"children": [
{
"id": "1",
"name": "Johnny" 
},
{
"id": "2",
"name": "Suzy"
}
]
}

MAPPINGS:
RKEntityMapping *parentMapping = [RKEntityMapping mappingForEntityForName:@"Parent" inManagedObjectStore:managedObjectStore];
parentMapping.identificationAttributes = @[@"id"];
[parentMapping addAttributeMappingsFromArray:@[@"id", @"name"]];
RKEntityMapping *childMapping = [RKEntityMapping mappingForEntityForName:@"Child" inManagedObjectStore:managedObjectStore];
childMapping.identificationAttributes = @[@"id"];
[childMapping addAttributeMappingsFromArray:@[@"id", @"name"]];

RELATIONSHIP CONNECTION:
[parentMapping addConnectionForRelationship:@"children" connectedBy:@{@"children": @"id"}];

RESPONSE DESCRIPTORS:
RKResponseDescriptor *parentResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:parentMapping pathPattern:@"/api/everything" keyPath:@"parents" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
RKResponseDescriptor *childResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:childMapping pathPattern:@"/api/everything" keyPath:@"children" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptorsFromArray:@[parentResponseDescriptor, childResponseDescriptor]];

The error I am getting is this:
'Cannot connect relationship: invalid attributes given for source entity 'Parent': children'

I have Core Data objects for both Parent and Child with a "children" relationship (to-many) from parent to child (and inverse specified on the child).

I think that I'm really close, but nothing I try seems to work. Suggestions?

Luke Docksteader

unread,
Dec 6, 2012, 2:12:33 PM12/6/12
to res...@googlegroups.com
Finally figured this out!

Instead of:
[parentMapping addConnectionForRelationship:@"children" connectedBy:@{@"children": @"id"}];
I used the following:
[parentMapping addRelationshipMappingWithSourceKeyPath:@"children" mapping:childMapping];

...and everything works now! It looks to me that addConnectionForRelationship:connectedBy is designed for connecting a to-one relationship where addRelationshipMappingWithSourceKeyPath:mapping is designed for connecting a to-many relationship. If someone wants to confirm/deny that please chime in.

Blake Watters

unread,
Dec 6, 2012, 11:48:39 PM12/6/12
to res...@googlegroups.com
You need to map the list of child ID's onto the parent model. I would add a childrenIDs attribute to the parent model, then do:

[parentMapping addAttributeMappingsFromDictionary:@{ @"children.id": @"childrenIDs" }];

Then do

[parentMapping addConnectionForRelationship:@"children" connectedBy:@{@"childrenIDs": @"id"}];

When establishing a connection, you are working off of attributes in the entities. Connect the relationship by values stored on the entities.

As I was writing this response for you ealier, I did a quick check to make sure that I had not broken the array support for attribute values when I added compound key support last week… It turns out that I fucked it up quite badly and it would produce exceptions. Some of the tests covering this functionality were left disabled during 0.20 development…

In any case, I tackled it this evening and have pushed the support in https://github.com/RestKit/RestKit/commit/79e31b524a8e02aa0d8822eb4da01950250aec7c

You can now connect relationships using one or more attributes that contain array values. All arrays are interpretted as logic OR's, so connecting to zipCodes: [1234, 5678], state: New York would mean connect the objects where zip code is 1234 or 5678 AND the state is New York.

On Thu, Dec 6, 2012 at 12:03 PM, Luke Docksteader <lu...@docksteaderluke.com> wrote:
connectedBy:@{@"children"

Blake Watters

unread,
Dec 6, 2012, 11:54:09 PM12/6/12
to res...@googlegroups.com
Not exactly. Relationship mappings are for mapping nested relationships within the graphs. Say you load an articles resources that has nested JSON for the 'comments' of the article. A relationship mapping will let you directly map those from the nested representation.

A connection, in contrast, lets you establish a relationship to objects that are created **across different requests**. So in the articles/comments example, imagine that instead we sent down "commentIDs" 1234, 5678 and then in another request we load the comments. A connection lets you hook up the Core Data relationship even though they were not loaded by the same payload -- you just connect them using matching attribute values.

Both connections and relationships handle one to one and one to many.

Luke Docksteader

unread,
Dec 7, 2012, 9:40:11 AM12/7/12
to res...@googlegroups.com
Thanks Blake,

That make sense. With that in mind, I've changed all of my many-to-one relationships (in the JSON) from "xxxID":"y" to "xxx":{"id":"y"} and used the addRelationshipMappingWithSourceKeyPath:mapping method (instead of addConnectionForRelationship:connectedBy) to connect them up. That seems to work best for linking objects that are all delivered in the same payload. I was already doing this with my many-to-many relationships so they were already working.

Mike

unread,
Dec 14, 2012, 9:24:46 AM12/14/12
to res...@googlegroups.com
I'm trying to perform the same task as Luke. I have a JSON with arrays in it containing IDs of other objects. I mapped everything as described in your previous post, and the app runs just fine (instantiating my one to many relationships) but my many to many relationships are not created. 

My JSON is like: 

{
      "name": "Name",
      "localita": [
        {"id": 2},
        {"id": 3}
      ]
}

and I have created the following mapping:

[mapping addAttributeMappingsFromDictionary:@{@"localita.id": @"localitaIDs" }];

[mapping addConnectionForRelationship:@"localita" connectedBy:@{@"localitaIDs" : @"place_id"}];

where place_id is the attribute of my destination entity and localitaIDs is a property in my parent entity:

@property (nonatomic, retain) NSNumber * localitaIDs;

@property (nonatomic, retain) NSSet *localita;

What am I missing?

Mike

unread,
Dec 14, 2012, 9:31:28 AM12/14/12
to res...@googlegroups.com
Has it something to do with the entity's attribute type? 
I used a @property (nonatomicretainNSNumber * localitaIDs;, but maybe I should have used something else... how is it storing the array from the JSON?

Luke Docksteader

unread,
Dec 14, 2012, 9:47:36 AM12/14/12
to res...@googlegroups.com
Hey Mike, 

From where are your "localita" objects coming? Are they also in the same JSON payload? If so, try using:

[mapping addRelationshipMappingWithSourceKeyPath:@"localita" mapping:localitaMapping];

...instead of the two lines (addConnectionForRelationship...) that you posted. To do the above, you also need to create a mapping for the localita entity, as well as a Core Data entity. Also, you need to have a relationship named "localita" on your first entity that is defined as a to-many relationship.

Like Blake mentioned above, addConnectionForRelationship is designed for establishing relationships across different requests. Personally, I have yet to get "to-many" connections working properly, but since I am sending multiple root objects in one payload, I don't need to (at least I don't yet).

I hope that helps...

Luke

Luke Docksteader

unread,
Dec 14, 2012, 9:51:40 AM12/14/12
to res...@googlegroups.com
As to how the relationship is stored: Core Data stores it as a join table between your first entity and the localital entity (like any other database) as far as I know.

Luke

Mike

unread,
Dec 14, 2012, 9:54:48 AM12/14/12
to res...@googlegroups.com
'localita' is a relationship (NSSet in NSManagedObject) with an entity in my CoreData model called 'Place' (hence the 'place_id' attribute in my mapping). Problem is objects are coming from different payloads (I'm getting different JSON responses for different objects), so I think I should stick with connections as described by Blake. What attribute type should 'localitaIDs' be?

Luke Docksteader

unread,
Dec 14, 2012, 10:02:09 AM12/14/12
to res...@googlegroups.com
In that case, you're right, stick with the connection. As to what type of attribute localitaIDs should be stored as, I do not know. I recall running into the same issue but never solved it. Maybe Blake can chime in?

Blake Watters

unread,
Dec 14, 2012, 11:57:42 AM12/14/12
to res...@googlegroups.com
Mike pinged me via Skype on this.

Here is the deal: If you wish to connect a to-many collection, you need to do the following on your Core Data entity:

1) Declare a transformable property on the Entity. This will be used to back your set of object ID's. In this case, we'd add a 'localitaIDs' transformable property.
2) Declare a property on your NSManagedObject subclass. This should be an NSSet or NSArray -- this is where your mapped collection of object ID's is going to be stored: @property (nonatomic, strong) NSArray *localitaIDs;

The rest of the mapping configuration as outlined is correct. What will happen is:

1) localita.id will be mapped to localitaIDs, valueForKeyPath:@"localita.id" will give you an array containing all of the ID's.
2) The property is set on the NSManagedObject. Core Data will store it the array and transparently serialize it to a property list. You get this for free from Core Data for NSArray, NSDicitonary, etc. becuase they are NSCoding conformant.
3) The connection code will kick in, see that you are trying to connect a collection of values and look up all of the objects matching the ID's, then set that as the value of the relationship.

There are examples in the unit tests if you want some code to follow, but that's the process at a high level.

On Fri, Dec 14, 2012 at 10:02 AM, Luke Docksteader <lu...@docksteaderluke.com> wrote:
in

Matej Bukovinski

unread,
Jan 9, 2014, 4:46:08 AM1/9/14
to res...@googlegroups.com
Hi, 

I apologize for bringing this old discussion back to life, but this seemed the best place to ask this question, so it can serve as a future reference for anyone else exploring this topic.

Is it possible to get away with just defining a NSArray property on the NSManagedObject (without a persistent transient core data attribute) and still have relationships properly connected? The related objects would be side-loaded in the same response. We're doing this instead of nesting the objects in order to remove duplicates. It seems unfortunate that I would need to clutter up my model with a bunch of arrays due to this.

Regards,
Matej
Reply all
Reply to author
Forward
0 new messages