Context
I'm writing an abstraction layer that translates Swift model objects into Couchbase database records and back again. I'm struggling to do this in a generic, extendable way with KeyPath
.
Consider this model object:
class ModelObject
{
var id: UUID = UUID()
var title: String = ""
var count: Int = 42
}
The Couchbase Swift SDK fetches the stored record of this object and gives me back a simple [String: Any?]
dictionary that I can enumerate. The keys are the names of each property on ModelObject
.
Attempt
I'm trying to reconstitute ModelObject
from the Couchbase dictionary. But I want to do that in a generic, extensible way so that I can handle any kind of properties on ModelObject
. I figured I could write a macro that maps string keys to AnyKeyPath
, like this:
class ModelObject
{
class var keyPathMap: [String: AnyKeyPath]
{
["id": \ModelObject.id,
"title: \ModelObject.title,
...]
}
}
But the problem is that I can't use these to set values because there's no way to cast them to ReferenceWritableKeyPath
dynamically.
Even though the rootType
and valueType
properties of each AnyKeyPath
in keyPathMap
are set and valid and ready-to-go, there's no way for me to tell Swift: "Look, you've got all the information you need. Just make the thing a ReferenceWritableKeyPath
so I can use it. The only reason it's type-erased is so that I could stick it in a collection. The types are there!"
Is there a way for me to do this? SwiftData's schema maps string names to AnyKeyPath
. How do they get a ReferenceWritableKeyPath
out of that for any possible value type? The only way I can do it is by manually writing out a giant if
waterfall:
if let writableKeyPath = erasedKeyPath as? ReferenceWritableKeyPath<Self, String>
{
...
}
else if let writableKeyPath = erasedKeyPath as? ReferenceWritableKeyPath<Self, Int>
{
...
}
else if...
I really don't want to fall back to @dynamicMemberLookup
. My current fallback plan is to just abandon KeyPaths and have a macro generate a function like this for each Model class that adopts my "Couchable" protocol:
func set(value: Any? for key: String)
{
if key == "title"
{
if let s = value as? String {
self.title = s
}
}
... // [repeat for all model properties]
}
Is there any way to adapt KeyPaths in this situation?