Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Retrieve property name from lambda expression in TypeScript

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?

like image 401
olivierr91 Avatar asked Jun 20 '18 13:06

olivierr91


Video Answer


2 Answers

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];
   //...
}
like image 70
Jonas Wilms Avatar answered Oct 01 '22 07:10

Jonas Wilms


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!

like image 33
jcalz Avatar answered Oct 01 '22 07:10

jcalz