TypeScriptで型からプロパティ名をとりたいけど...

C#ではリフレクションで簡単に取れる。

IEnumerable<string> propNames = typeof(T).GetProperties().Select(p => p.Name);

同じようなことをしたい。TypeScriptはJavaScriptにトランスパイルされる。つまり、型があるのはコンパイル時点までだから、リフレクションみたいな実行時の解決はできないはず。だから、あるとしたら言語サポートの特殊キーワードだと思うんだけど見つからない。

typeof も keyof も nameof もちゃうんよ。

できないのかなあ。

ほしい用途としてはAPI周り。

async function apiPost<T>(body: T) {
	const res = await fetch(url, { 
		method: 'post', 
		headers:{'Content-Type':'application/json'},
		body: JSON.stringify(body)
	});
  return await res.json();
}

type ReqBody = {
	prop1: string,
	prop2: string,
}

await apiPost<ReqBody>({prop1: '', prop2: '', prop3: ''}); // これは余剰プロパティチェック (excess property checking)で意図通りエラーになる。

let req1: ReqBody;
req1 = {prop1: '', prop2: '', prop3: ''};
await apiPost(req1); // これはエラーにならない

const req2: ReqBody = JSON.parse('{"prop1":"", "prop2":"", "prop3":""}');
await apiPost(req2); // これもエラーにならない

APIによっては、リクエストに余剰なプロパティがあると400になるものも多い。 TypeScriptには余剰プロパティチェックがあるので、余剰なプロパティがペイロードに入り込むことなんてないぜって思いきや、そうでもない。 場合にもよるけど、チェックが走らないケースは多い。

つまり、apiPostのパラメータ型定義は、最小限その型を満たすプロパティがあるということだけしか保証してない。

どんな引数でもちゃんとPOSTできるようにしようとするとこうなる。

async function apiPostReqBody(body: ReqBody) {
	const res = await fetch(url, { 
		method: 'post', 
		headers:{'Content-Type':'application/json'},
		body: JSON.stringify({
			prop1: body.prop1, // さあ、詰め替えの時間だ!
			prop2: body.prop2,
		})
	});
  return await res.json();
}

詰め替えないといけないので、ジェネリックは諦めなきゃいけないし、propの数だけ頑張らないといけない。 まあ、明白になるから悪くないといえなくもないけど、ぶっちゃけつらい。propが20,30あったらそれはもうつらい

ここで型からプロパティ名がstring[]でとれれば、mapper作れるのに。

もう1つあると便利だなと思うケース。

async function apiGet<T>(path:string, select: string[] = []) {
	let url = `${ep}/${path}?&select=${select.join(',')}`; 
	const res = await fetch(url);
  return await res.json();
}
type User = {
	id: number,
  email: string,
  name: string,
  birthday: string,
  ...
}
type IdAndEmailOfUser = Pick<User, 'id'|'email'>

const userinfo = await apiGet<IdAndEmailOfUser>('users', ['id','email']);

リクエストパラメータでレスポンスに含めるプロパティを絞れるAPIは結構ある。ODataとか。

このとき、型定義から、プロパティ名がとれれば、 'id''email'を2回書かなくていいのに。

型からプロパティ名をとる方法はほんとにないんかあ