I am having trouble configuring a dynamic mapping for an array of results. The result objects I'd like to map are embedded within a "container" that describes the type of the enclosed object.
I have JSON similar to the following:
"list": {
"name": ""
"item_infos": [
{
"type": "TaskItem",
"item": {
"id": 0
"title": "A Task Item",
"task": "..."
}
"url": "..."
},
{
"type": "ReminderItem",
"item": {
"id": 0
"title": "A Reminder Item",
"reminder": "..."
}
"url": "..."
}]
}
I'd like to map this as a List
object with an array of Item
s, where each item might be of a different type. The different type
s are finite and known.
class List: NSManagedObject {
@NSManaged var name: String
@NSManaged var items: NSOrderedSet
}
class Item: NSManagedObject {
@NSManaged var identifier: Int32
@NSManaged var title: String
// Each mappable `Item` is responsible for providing its own
// mapping object. It's overridden in the subclasses below.
class func responseMappingForManagedObjectStore(dos: RKManagedObjectStore) -> RKEntityMapping { ... }
}
class TaskItem: Item {
@NSManaged var task: String
}
class ReminderItem: Item {
@NSManaged var reminder: String
}
How can I map the embedded item
directly under the list using RKDynamicMapping
while still making use of the type
field? I'm trying something along these lines for the response mapping:
let listMapping = RKEntityMapping(forEntityForName: "List", inManagedObjectStore: store)
responseMapping.addAttributeMappingsFromDictionary(["name": "title"])
let dynamicItemMapping = RKDynamicMapping()
dynamicItemMapping.setObjectMappingForRepresentationBlock { representation in
let itemMapping: RKEntityMapping
switch representation.valueForKeyPath("type") as? String {
case .Some("TaskItem"): itemMapping = TaskItem.responseMappingForManagedObjectStore(objectStore)
case .Some("ReminderItem"): itemMapping = ReminderItem.responseMappingForManagedObjectStore(objectStore)
default: return nil
// This is the bit I'm failing to solve. How can I return a mapping
// that essentially "skips" a level of the JSON, and just maps the
// embedded `item`, not the `item_info`.
let itemInfoMapping = RKObjectMapping(forClass: NSMutableDictionary.self)
itemInfoMapping.addRelationshipMappingWithSourceKeyPath("item", mapping: itemMapping)
return itemInfoMapping
}
listMapping.addPropertyMapping(RKRelationshipMapping(
fromKeyPath: "item_infos",
toKeyPath: "items",
withMapping: dynamicItemMapping))
With this mapping, an exception is raised:
-[__NSDictionaryM _isKindOfEntity:]: unrecognized selector sent to instance 0x7fd2603cb400
Which doesn't surprise me, as the way the dynamic mapping is set up just doesn't feel right anyway – I'm looking for a way to "skip" a level of the JSON and only map the embedded item
.
An alternative attempt was to fully specify the fromKeyPath
as "item_infos.item"
for the relationship to the listMapping
, but then I cannot use the type
field in the dynamic mapping block to determine the type of Item
mapping to use:
// ...
dynamicItemMapping.setObjectMappingForRepresentationBlock { representation in
// `type` inaccessible from the nested item representation
switch representation.valueForKeyPath("type") as? String {
case .Some("TaskItem"): return TaskItem.responseMappingForManagedObjectStore(objectStore)
case .Some("ReminderItem"): return ReminderItem.responseMappingForManagedObjectStore(objectStore)
default: return nil
}
listMapping.addPropertyMapping(RKRelationshipMapping(
fromKeyPath: "item_infos.item",
toKeyPath: "items",
withMapping: dynamicItemMapping))
You can't do exactly what you're trying to do, because you're connecting a core data relationship to what will be an array of dictionaries which contains managed objects.
The simplest solution is to take your skip logic out from where it is and create all of the mappings for the managed objects (task and reminder) by adding item.
at the start of the source key (path). So
"item.title" : "title"