Write access to struct fields by string name

I want to write some generalized code that can write values to struct fields given the field name as a string. I've looked at various options and haven't settled on something I like yet.

  • WriteableKeyPath doesn't seem to have a way to construct it from a string.
  • Codable obviously does something with string-based access (and the structs I'm working with are all codable), but it all happens behind the scenes.
  • @objc members could work, using good old KVC, but I keep running into cases that are not objc-compatible, like enums, and I don't want to let this string-based access feature dictate which data types I can use.
  • Mirror, at least in its public interface, only provides read access. The recent Swift Blog post How Mirror Works mentions the ability to "manipulate arbitrary values at runtime" but I didn't see how to actually do that.

The last option I'm considering is to have code that just looks up the fields using switch statements. Ideally, for best maintainability, I'd write something that processes the swift file, perhaps using SourceKit, and generates the accessor methods I would need.

Are there any other approaches to consider? Or is there a reason to recommend one of the things I already looked at?

It's possible I could get WritableKeyPath to work for me, and not necessarily have to work with strings, but I haven't figured it out.

For example, when I try to assign a key path, I'm getting:

Cannot convert value of type 'KeyPath<MyStruct, String?>' to expected argument type 'WritableKeyPath<_, String?>'

The field I'm referencing is declared as var myField: String = nil, and I thought that since it's var rather than let I should be getting a WritableKeyPath.

Since the Swift compiler fully type checks key paths at compile time, you can't simply compose them from arbitrary strings. This is different from Objective-C, where you can convert strings to selectors and call them on arbitrary objects. Below is a brief example of using a key path. Note that the type of the keyPath constant is determined by the compiler to be WriteableKeyPath<Person, Int>. If you were to change var age: Int to let age: Int, the type of keyPath would change at compile time to KeyPath<Person, Int>— in other words, it would no longer be writable.

struct Person {
    var name: String
    var age: Int
}

var joe = Person(name: "Joe", age: 25)
let keyPath = \Person.age
joe[keyPath: keyPath] = 26
print(joe)