I want to define an Array type that must contain a chain of nested property names of a given type.
Let's say I have a type:
type Foo = {
outer: {
inner: any;
}
}
Now I want to define an Array type with 2 elements:
type PropertyList<T, K1 extends keyof T, K2 extends keyof T[K1]> = [K1, K2];
I want to use it like this:
let myList:PropertyList<Foo> = ["outer", "inner"]
So I want the compiler to check if the 2 contained Property names are nested property names of Foo.
But I can't define PropertyList with only 1 generic parameter, I get this error then:
TS2314: Generic type 'PropertyList' requires 3 type argument(s)
Any idea how i can infer the nested keyof types without having to specify them?
Edit
I think I misunderstood your question and don't think that's possible (yet). Maybe inferring the positional types will be possible with type inference for conditional types that will be included in the upcoming version 2.8 of TypeScript.
I will leave the original answer below just in case.
Type parameters can have default values:
type PropertyList<T, K1 extends keyof T = keyof T, K2 extends keyof T[K1] = keyof T[K1]> = [K1, K2];
But maybe in your case it makes more sense to not specify K1
and K2
as type parameters:
type PropertyList<T> = [keyof T, keyof T[keyof T]];
A first approximation of how to do this is in Tao's answer, the problem is that if you add more properties it does not work as expected:
type Foo = {
outer: {
inner: any;
}
outer2: {
inner2: any;
}
}
type PropertyList<T, K1 extends keyof T = keyof T, K2 extends keyof T[K1] = keyof T[K1]> = [K1, K2];
let myList:PropertyList<Foo> = ["outer", "inner"] // error, since K2 will have to be a property of both outer and outer2
You can use function to help with inferring the corect type based on the actual parameters passed. We need to use a two function approach because we need to have generic parameters for T
, K1
and K2
, but we only want to specify T
and if we specify one we must specify all parameters:
function pathFor<T>() {
return function <K1 extends keyof T, K2 extends keyof T[K1]>(outer: K1, inner: K2): [K1,K2]{
return [outer, inner];
}
}
// Usage
let myList = pathFor<Foo>()("outer", "inner"); // typed as ["outer, "inner"]
let myList2 = pathFor<Foo>()("outer2", "inner"); // error, inner is not part of outer2
let myList3 = pathFor<Foo>()("outer2", "inner2"); // typed as ["outer2, "inner2"]
Edit
You can also expand the function to take paths up to a finite length (4 in the example, but add more as needed):
function keysFor<T>() {
function keys<K1 extends keyof T, K2 extends keyof T[K1], K3 extends keyof T[K1][K2], K4 extends keyof T[K1][K2][K3]>(outer: K1, inner: K2, innerInner: K3, innerInnerInner: K4): [K1,K2, K3, K4]
function keys<K1 extends keyof T, K2 extends keyof T[K1], K3 extends keyof T[K1][K2]>(outer: K1, inner: K2, innerInner: K3): [K1,K2, K3]
function keys<K1 extends keyof T, K2 extends keyof T[K1]>(outer: K1, inner: K2): [K1,K2]
function keys(): string[]{
return [...arguments];
}
return keys
}
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