Reputation: 12637
Is there a way to get/use the actual property name in an Typescript type definition.
type Mapped = {
[P:string]: P;
}
Where Mapped["foo"]
is of type "foo"
and Mapped["bar"]
is of type "bar"
, not string
.
I tried different variations on [P:string]
, [P in keyof any]
, typeof P
, readonly
and as const
or type Mapped<Props extends keyof any> = { [P in Props]: P }
and everything I came up with along these lines, but P
is at best typed as string
, never as the actual property name.
What do I need this for?
To build upon. Typing Proxies that dynamically generate utility functions using the accessed property name.
Something like
type GetProperty = {
[P in (keyof any)]: <T>(obj:T) => P extends keyof T ? T[P] : void;
}
My issue is, that since the generated/returned values are dynamic, I don't have a type T
to keyof T
the names from.
Could you please provide an example of expected bahaviour?
var obj:Mapped = new Proxy(...);
// where the properties of `obj` should be typed as
var foo:"foo" = obj.foo;
var bar:"bar" = obj.bar;
And obj.asdf1234
should be of type "asdf1234"
Upvotes: 0
Views: 350
Reputation: 327624
This cannot be done in TypeScript. There is an open feature request at microsoft/TypeScript#22509 which asks for this functionality, and while you should feel free to go there and give it a 👍 or describe your use case if you think it's particularly compelling, I'd say that there's a low chance it'll happen. From this comment:
It is simply not possible to model that with our current type system features (because nothing allows you to capture the literal type corresponding to the property name in a property access).
So I wouldn't hold my breath if I were you.
My suggestion is to give up on dynamic property keys and instead try a single method takes the desired key as an argument:
const obj = {
get<P extends PropertyKey>(p: P) {
return <T,>(t: T): P extends keyof T ? T[P] : void =>
(t as any)[p]
}
}
const foo = {
a: 1,
b: "two",
c: true
}
console.log(obj.get("a")(foo).toFixed(2)); // 1.00
console.log(obj.get("b")(foo).toUpperCase()); // TWO
obj.get("z")(foo) // void
Or, at that point, you might as well just use a function instead of a method:
function getter<P extends PropertyKey>(p: P) {
return <T,>(t: T): P extends keyof T ? T[P] : void =>
(t as any)[p]
}
console.log(getter("a")(foo).toFixed(2)); // 1.00
console.log(getter("b")(foo).toUpperCase()); // TWO
getter("z")(foo) // void
The idea is that instead of obj.a(foo)
you have either obj.get("a")(foo)
or just getter("a")(foo)
. It has the same functionality but it's packaged a little differently. It might not be the exact form you want, but at least it has the advantage that TypeScript can represent what it's doing!
Upvotes: 2