6

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?

2 Answers 2

3

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
}
Sign up to request clarification or add additional context in comments.

1 Comment

This "two function approach" is a brilliant workaround to the lack of support for partial generic argument specification. I'd like to be able to assign a type to myList, though, such that it can hold the results of any call to pathFor<Foo>()(...). I'm still working on figuring out that declaration.
2

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]];

1 Comment

I know this is an old answer so maybe the result is dependent on TS version, but in my experiments keyof T[keyof T] is always never regardless of what constraints you place on T. ETA: never mind, I think Titian's answer clarifies the problem.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.