Get specific CodingKey for KeyPath
Asked Answered
B

2

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"
Berchtesgaden answered 1/10, 2017 at 11:32 Comment(3)
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?Wardroom
@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)...Wealthy
... 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.Wealthy
G
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.)

Glaab answered 7/10, 2017 at 17:6 Comment(0)
C
-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: "[email protected]", name: "just_name")
Mirror(reflecting: user).children.first?.label
Colonnade answered 1/10, 2017 at 13:2 Comment(1)
This is not what OP asked. What if the user it is using custom keys? #44397000Illstarred

© 2022 - 2024 — McMap. All rights reserved.