Determining the type of nested keyof Properties in Typescript

Looking to create an Array type that contains a chain of nested property names with a specific type requirement.

Imagine having the following type:

    type Foo = {
        outer: {
            inner: any;
        }
    }

Now, I aim to define an Array type consisting of 2 elements:

type PropertyList<T, K1 extends keyof T, K2 extends keyof T[K1]> = [K1, K2];

The intended usage would be like this:

let myList:PropertyList<Foo> = ["outer", "inner"]

Hence, I want the compiler to validate if the 2 specified property names are indeed nested within Foo.

Encountering an issue when trying to declare PropertyList with only one generic parameter results in the error message:

TS2314: Generic type 'PropertyList' requires 3 type argument(s)

Is there a way to automatically deduce the nested keyof types without manually specifying them?

Answer №1

The initial approach to solving this issue is outlined in Tao's response, but the challenge arises when additional properties are added and it does not function as anticipated:

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"] // This results in an error because K2 must be a property of both outer and outer2

To address this issue, we can utilize a function to assist with inferring the correct type based on the provided parameters. A two-function approach is necessary here as generic parameters for T, K1, and

K2</code are required, but only <code>T
should be specified:

function pathFor<T>() {
    return  function <K1 extends keyof T, K2 extends keyof T[K1]>(outer: K1, inner: K2): [K1,K2]{
        return  [outer, inner];
    }
}
// Example of 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

Another option is to expand the function to accommodate paths up to a finite length (4 in this example, but more can be added 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
}

Answer №2

Update

Upon further reflection, it appears that achieving the desired outcome may not be feasible at this time. However, there is potential for inferring positional types through type inference for conditional types, which is set to be implemented in the upcoming TypeScript version 2.8.

The original response remains intact below as a reference.


In TypeScript, default values can be assigned to type parameters like so:

type PropertyList<T, K1 extends keyof T = keyof T, K2 extends keyof T[K1] = keyof T[K1]> = [K1, K2];

Alternatively, you may opt to omit specifying K1 and K2 as individual type parameters based on your specific needs:

type PropertyList<T> = [keyof T, keyof T[keyof T]];

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

When using `useSWR`, it will return { null, null } for a successful request

When attempting to query the Firebase real-time database using useSWR in my next.js project, I encounter a perplexing issue where both the data and error variables always return as null. import useSWR from 'swr'; const LastSales: NextPage = () = ...

Tips for incorporating JavaScript modules into non-module files

Learning how to use js modules as a beginner has been quite the challenge for me. I'm currently developing a basic web application that utilizes typescript and angular 2, both of which heavily rely on modules. The majority of my app's ts files ...

Introducing a delay in an observable causes incomplete data to be received in Angular using rxjs

Currently, I am facing an issue in my code where I am trying to introduce a delay using timer(500). However, the problem is that it is only returning partial data. Instead of the expected 17 fields, it is only returning 2 fields. Below is my code snippet f ...

Transform a string into a class in Typescript/Angular

In my application, I've created a reusable modal popup component that takes a string as input and dynamically loads other components based on that input. This approach allows me to use the same modal popup component for multiple modals in the app inst ...

Dealing with server-side errors while utilizing react-query and formik

This login page utilizes formik and I am encountering some issues: const handleLogin = () => { const login = useLoginMutation(); return ( <div> <Formik initialValues={{ email: "", password: "" }} ...

Is there a way to display the input value from an on-screen keyboard in an Angular application?

https://i.sstatic.net/j76vM.pnghttps://i.sstatic.net/EQPZO.png I have included my code and output snippet below. Is there a better way to display the input value when clicking on the virtual keyboard? ...

Creating an Http interceptor in Ionic 3 and Angular 4 to display a loading indicator for every API request

One of my current challenges involves creating a custom HTTP interceptor to manage loading and other additional functions efficiently. Manually handling loading for each request has led to a considerable increase in code. The issue at hand: The loader is ...

What is the procedure for accessing a namespace when declaring it globally?

Website Project Background Currently, I am working on a simple website where users can update their pictures. To achieve this functionality, I am utilizing the Multer library along with Express in Typescript. Encountered Issue I am facing a challenge re ...

Having difficulty invoking the forEach function on an Array in TypeScript

I'm currently working with a class that contains an array of objects. export interface Filter { sf?: Array<{ key: string, value: string }>; } In my attempt to loop through and print out the value of each object inside the array using the forE ...

Highlight the active class on the Angular Navbar

I have been successfully using [routerLinkActive]="['active']" in my application to add an active class when the button on navbar is clicked and redirects to example.com/home. However, I noticed that if I only navigate to example.com, the active ...

What is the methodology behind incorporating enumerations in typescript?

I've been curious about how typescript compiles an enumeration into JavaScript code. To explore this, I decided to create the following example: enum Numbers { ONE, TWO, THREE } Upon compilation, it transformed into this: "use strict ...

Calculate the variance between two variables

I am facing a challenge where I have an object and the 'Hours' field is saved as a string. I am looking to convert this string into actual hours and then calculate the difference between the two variables. const groupSchedule=[ {"days":"sat" ...

TypeScript Error: The Object prototype must be an Object or null, it cannot be undefined

Just recently, I delved into TypeScript and attempted to convert a JavaScript code to TypeScript while incorporating more object-oriented features. However, I encountered an issue when trying to execute it with cmd using the ns-node command. private usern ...

Why is it considered an error when an index signature is missing in a type?

Consider the TypeScript code snippet below: type Primitive = undefined | null | boolean | number | string; // A POJO is simply meant to represent a basic object, without any complexities, where the content is unknown. interface POJO { [key: string]: ...

What is the most appropriate form to use, and what factors should be considered in determining

Incorporating generics in typescript allows me to create a generic function in the following manner: Choice 1 declare function foo1<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; } Alternatively, I have the option to omit the seco ...

Creating a new instance with a parameter in TypeScript using a generic function

In my code, I have numerous classes that extend an interface. These classes can contain arrays of objects from each other. For example, a school can have multiple students, but both classes implement the same interface. To streamline this process, I decid ...

Is it possible to create an instance in TypeScript without using class decorators?

As per the definition on Wikipedia, the decorator pattern enables you to enhance an object of a class with additional functionalities, such as: let ball = new BouncyBall(new Ball()) The Ball object is adorned with extra features from the BouncyBall class ...

What is the best way to transfer values between various methods within a single service file in Angular?

In my service file, I have two methods called getData and delete. The data is sourced from an API and the getData method works fine. However, I am facing a problem with the delete() method where siteId is not being read correctly. When I click the save bu ...

Is there a way to deactivate a tab when it's active and reactivate it upon clicking another tab in Angular?

<a class="nav-link" routerLink="/books" routerLinkActive="active (click)="bookTabIsClicked()" > Books </a> I am currently teaching myself Angular. I need help with disabling this tab when it is active ...

Leverage the specific child's package modules during the execution of the bundle

Project Set Up I have divided my project into 3 npm packages: root, client, and server. Each package contains the specific dependencies it requires; for example, root has build tools, client has react, and server has express. While I understand that this ...