6

Consider the following object:

struct User: Codable {
    let id: Int
    let email: String
    let name: String
}

Is it posible to get a specific CodingKey for a given KeyPath?

let key = \User.name.codingKey  // would be equal to string: "name"
Paulo Mattos
  • 18,845
  • 10
  • 77
  • 85
Wiingaard
  • 4,150
  • 4
  • 35
  • 67
  • Autogenerated `CodingKeys` are private to the type, so you won't be able to access them outside of that type. What are you looking to do here? – Itai Ferber Oct 02 '17 at 03:26
  • 2
    @ItaiFerber I don't know about OP's use case, but I also have often required some way to get a `CodingKey` from a `KeyPath`. Most recent use case was building a strongly-typed wrapper around the Firebase Database API, so instead of saying `someRef.child("foo").child("bar").observe(.value) { snapshot in /* snapshot.value is an Any? ew. */}` you would say `someProvider.observeValue(at: \.foo.bar) { value in /* value is typed as whatever the type of bar is */ }`. To implement, had to have each type provide a dictionary that mapped immediate child key paths to coding keys (in a 1:1 manner)... – Hamish Oct 02 '17 at 09:43
  • ... The coding keys were just typed as `CodingKey`, so `CodingKeys` didn't need to be exposed. Then had to iterate through the entire tree of properties from a given root type, building up a key path and comparing it with the argument; which wasn't ideal. It would certainly be nice if the language provided some way to automatically do that mapping for you; although that being said, I'm not sure it'd be viable for types that implement their own custom encoding/decoding logic. – Hamish Oct 02 '17 at 09:43

2 Answers2

4

Using Swift 4, I don't think you can automatically retrieve a CodingKey from the corresponding KeyPath object but you can always hack your way around it ;)

For instance, in the same User type Swift source file, add the following extension:

fileprivate extension User {
    static func codingKey(for keyPath: PartialKeyPath<User>) -> CodingKey {
        switch keyPath {
        case \User.id:    return CodingKeys.id
        case \User.email: return CodingKeys.email
        case \User.name:  return CodingKeys.name
        default: fatalError("Unexpected User key path: \(keyPath)")
        }
    }
}

then implement the desired codingKey API in the constrained KeyPath superclass:

extension PartialKeyPath where Root == User {
    var codingKey: CodingKey {
        return User.codingKey(for: self)
    }
}

Finally, the usage follows closely your code:

let name: CodingKey = (\User.name).codingKey
print("\(name)") // prints "name"

This may be a somewhat tedious and error prone solution but, if you only need this capability for handful of types, it's perfectly doable in my opinion ;)

Caveats. This hack, of course, won't work for externally defined types given CodingKeys enum private visibility. (For instance, for all Codable types defined by the Swift Standard Library.)

Paulo Mattos
  • 18,845
  • 10
  • 77
  • 85
-3

I don't think you can convert property directly to the string but you can achieve similar thing using reflection, but you have to create an instance of the struct:

let user = User(id: 1, email: "sample@email.com", name: "just_name")
Mirror(reflecting: user).children.first?.label
Rendel
  • 1,794
  • 3
  • 13
  • 16
  • This is not what OP asked. What if the user it is using custom keys? https://stackoverflow.com/questions/44396500/how-do-i-use-custom-keys-with-swift-4s-decodable-protocol/44396824?s=2|4.9822#44396824 – Leo Dabus Oct 01 '17 at 13:16