john_law
john_law

Reputation: 35

Types from all nested values of an object

I wonder if there's a better way to get all the types of the nested values of an object. My case in particular could be generalized to something of this sort:

const myObj = {
    group1: {
        subGroup11: () => {return {} as const},
        subgroup12: () => {return {} as const},
        ...
    },
    group2: {
        subGroup21: () => {return {} as const},
        subGroup22: () => {return {} as const},
        ...
    }
    ...
}

All the final values are functions.

What I am currently doing is being exhaustive, so I get all the types from each nested property:

type myType = <ReturnType
   typeof myObj.group1.subGroup12 |
   typeof myObj.group1.subGroup22 |
   ...
   typeof myObj.group2.subGroup21 |
   ...
>

Notes:

  1. The index notation for the property names is just for the example's sake.

  2. The groups don't necessarily have the same properties.

Update on Answers:

Check out Maciej's and leonard's answers, as they are absolutely on point. The latter approach checks the types of an object up to 2 levels deep, while the first works recursively to any depth, also note that this last solution works even if the values are not functions. Thank you Maciej, leonard, and jcalz for the contributions.

Upvotes: 1

Views: 115

Answers (2)

Maciej Sikora
Maciej Sikora

Reputation: 20162

We can make a generic type which will work with any nested structure. Here it is

type SelectFunctionRetunTypes<T extends object> =
    {
        [K in keyof T]
        : T[K] extends (...args: any) => any
        ? ReturnType<T[K]>
        : T[K] extends object
            ? SelectFunctionRetunTypes<T[K]>
            : T[K] // primary value not a function and not an object  
    }[keyof T]

type ReturnsMyObj = SelectFunctionRetunTypes<typeof myObj>;

SelectFunctionRetunTypes is recursive type, when our value T[K] is a function type then we take return type from it by ReturnType type level function, if it is not a function, we check if its an object, if it is we continue the same algorythm in this object by recursive call of SelectFunctionRetunTypes. The last part is when its not a function and not an object, in this case we just return a type of value we have.

This generic type with work with all kind of nested object types, it will gives return type if the value type is a function, and just value type if not.

Full example in The Playground

Upvotes: 1

leonardfactory
leonardfactory

Reputation: 3501

You should use a combination of mapped types, inference and fake recursion. Something along the line of T[K][keyof T[K]], using inference not only to get R (return type) but even to find if property at level 1 is Function or level 2 ("group" Object), like this:

type FlattenTwoLevels<T extends {}> = {
    [K in keyof T]: T[K] extends (...args: any) => any ? T[K] : T[K][keyof T[K]]
}[keyof T];

type ReturnMyObj = ReturnType<FlattenTwoLevels<typeof myObj>>;

I've left the out the (...args: any) => infer R inference, since ReturnType already works by default here, so the FlattenTwoLevels generic helper only needs to make the object usable by ReturnType.

Here is the Playground Link

Upvotes: 1

Related Questions