Thomas
Thomas

Reputation: 12637

Using property name in typescript declaration

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

Answers (1)

jcalz
jcalz

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!

Playground link to code

Upvotes: 2

Related Questions