I would like to strongly type property names
myMethod(model => model.userId);
public myMethod(model: () => any) {
//Must print "userId"
}
I already know this won't work because JavaScript will evaluate userId.
It's easily doable in C#:
Get string property name from expression
Retrieving Property name from lambda expression
Is it possible to do it in TypeScript/JavaScript?
Unlike in C#, one can dynamically access properties by their name in JavaScript (and thus also Typescript), so you could just pass the name as a string to the function and use bracket notation to access the property:
myMethod(model, "userId")
Now the cool thing about typescript that this string can actually be typesafe:
function myMethod<T, K extends keyof T>(model: T, key: K) {
const value = model[key];
//...
}
Read on
If you really want to do something similar like you did in C# (don't!!) Just do this:
function myMethod(model: () => any) {
const key = model.toString().split(".")[1];
//...
}
If I understand what you're asking, you'd like to inspect a property-retrieving arrow function, and return the name of the property it's returning? That sounds like a Bad Idea for a weakly/dynamically typed language like JavaScript, and something any sane person should run screaming from. Still, assuming I were either insane or being coerced, here's how I would go about trying to doing it in TypeScript 2.9 compiled to ES2015:
type ValueOf<T> = T[keyof T];
function evilMagic<T, V extends T[keyof T]>(
f: (x: T)=>V
): ValueOf<{[K in keyof T]: T[K] extends V ? K : never}>;
function evilMagic(f:(x: any)=>any): keyof any {
var p = new Proxy({}, {
get(target, prop) { return prop }
})
return f(p);
}
The function evilMagic
takes a property-getting function and tries to return the name of the property it returns. The type of the output is hard to explain, but basically it will be some subset of keyof T
where T
is the argument the property-getting function expects. The implementation of the function uses a Proxy object p
which acts like some object whose values are always the same as its keys. That is, p.foo
is "foo"
, and p.bar
is "bar"
, and p[10]
is "10"
. Call the property-getter on p
and, abracadabra, you have the name of the property.
Here's an example of its use:
interface Person {
name: string;
age: number;
numberOfLimbs: number;
}
const m = evilMagic((x: Person) => x.age); // typed as "age"|"numberOfLimbs";
console.log(m); // "age"
At compile time, TypeScript can only tell that m
is one of "age"
or "numberOfLimbs"
, because it only sees that the callback function returns a number
. At runtime, you get "age"
as you expect.
This whole thing scares me, though, and I feel unclean writing it; a function that expects a value of some type may do all kinds of crazy things when handed that proxy. I'd hate to think of code like that ending up in anything meant for production, although it could be a useful debugging tool. Please tell me you'd only use it for a debugging tool! 😓
Anyway, hope that helps. Good luck!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With