Is it possible to infer the type of a private member in a generic way?
Having this class:
class Dummy {
private num: number; // I want to get the type of this: number
str: string; // control group
}
I can get the type of the num
field manually:
type DummyNumTypeWorks = Dummy['num']; // number -- yey, I'm happy.
But I want to do this with generics. I have the following examples that DON'T WORK. And I understand why: Dummy
does not implement { num: number }
, it does not have a public num
member. So I'm searching for alternatives.
// Inferring from Dummy
type DummyMemberType<TMember extends string> = Dummy extends { [key in TMember]: infer R } ? R : never;
type DummyStrMember = DummyMemberType<'str'>; // string
type DummyNumMember = DummyMemberType<'num'>; // never :(
// Inferring from the member name
type DummyMemberType2<TMember extends string> = TMember extends keyof Dummy ? Dummy[TMember] : never;
type DummyStrMember2 = DummyMemberType2<'str'>; // string
type DummyNumMember2 = DummyMemberType2<'num'>; // never :(
I understand why these DON'T work.
My question: while I can infer the type of num
manually, is there a way to do this in a generic way (with more metaprogramming)?
Background: I want to create fancy decorators that give guarantees on some other members besides the one being decorated. And some of these members might be private.
Private members are not enumerable in keyof
.
But, as you noted, if you know the name you can dive and get it anyway.
Given that we can get closer with:
type DummyMemberType<TMember extends string> = Dummy[TMember];
// Type 'TMember' cannot be used to index type 'Dummy'.(2536)
type DummyStrMember = DummyMemberType<'str'>; // string
type DummyNumMember = DummyMemberType<'num'>; // number
type DummyBadMember = DummyMemberType<'bad'>; // unknown
There's still a type error inside the type, but DummyMemberType
now works as it should when used. However, if you pass in a bad key, you get unknown
, which isn't ideal.
So we need to make Dummy
indexable by any string to allow us to examine its non enumerable keys. If we intersect it with an index signature we can tell typescript that it's safe to check any string property.
type DummyMemberType<TMember extends string> =
(Dummy & { [key: string]: never })[TMember];
Which you could abstract to a generic type alias like so:
type UniversallyIndexable<T> = T & { [key: string]: never }
type DummyMemberType<TMember extends string> =
UniversallyIndexable<Dummy>[TMember];
Now it works like you expect:
type DummyStrMember = DummyMemberType<'str'>; // string
type DummyNumMember = DummyMemberType<'num'>; // number
type DummyBadMember = DummyMemberType<'bad'>; // never
Ideally, DummyMemberType<'bad'>
would be a type error, but without being able to constrain the key to keyof Dummy
, I don't see how that's possible. Returning never
may be as good as it gets.