A Typescript type that verifies whether a provided key, when used in an object, resolves to an array


I have a theoretical question regarding creating an input type that checks if a specific enum key, when passed as a key to an object, resolves to an array. Allow me to illustrate this with an example:
enum FormKeys {
  x = "x",
  y = "y",
}

interface InitialFormValues {
  [FormKeys.x]: number
  [FormKeys.y]: string[]
}

const initialFormValues: InitialFormValues = {
  [FormKeys.x]: 123,
  [FormKeys.y]: ["a"],
}

function onInputUpdate(input: FormKeys) {
  /*
    There's an error here because assigning an array to a property 
    defined as type number (in the case of FormKeys.x) is incorrect
  */
  initialFormValues[input] = []
}

onInputUpdate(FormKeys.y)

One approach could be changing input: FormKeys to input: FormKeys.y, but this solution may not be scalable. Is there a more general way to achieve this?
Thank you.

Answer №1

If you want to identify keys that correspond to array types, you can achieve this by utilizing the following approach (found it here):

type ArrayKeys<T> = {
    [Key in keyof T]: T[Key] extends any[] ? Key : never;
}[keyof T];

(The explanation for this is provided below.) For example,

ArrayKeys<IInitialFormValues>
would simply be FormKeys.b, as it is the only property with an array type. If there are multiple properties with arrays,
ArrayKeys<IInitialFormValues>
would represent a union of those keys.

Subsequently, the type for your input parameter should be

ArrayKeys<IInitialFormValues>
:

function onChange(input: ArrayKeys<IInitialFormValues>) {
    initialFormValues[input] = [];
}

This method ensures that the following function call works as intended:

onChange(FormKeys.b);   // Works as desired

While this one fails gracefully:

onChange(FormKeys.a);   // Fails as desired

You can see a live demonstration on the TypeScript Playground type ArrayKeys<T> = { [Key in keyof T]: T[Key] extends any[] ? Key : never; }[keyof T];

There are two steps involved in this process:

  1. The section enclosed in {/*...*/} maps each property of T to a new mapped type where the property's type serves as the key or returns never. Using the example:

    type Example = {
        a: string[];
        b: number[];
        c: string;
    };
    

    The first part of ArrayKeys creates the following anonymous type:

    {
        a: "a";
        b: "b";
        c: never;
    }
    
  2. Lastly, the [keyof T] portion at the end generates a union of the property types. In theory, this would result in

    "a" | "b" | never
    , but since never gets removed from unions, we end up with just "a" | "b" — representing the keys of Example for properties with array types.

You could further refine this and construct a type that specifically includes portions of Example matching these criteria using

Pick<Example, ArrayKeys<Example>>
. However, such refinement is unnecessary for the current context.

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

Unable to transfer the form value to the service and City value cannot be updated

I am new to the world of Angular and I am attempting to create a basic weather application. However, I am encountering issues when trying to pass the city value from the form on ngSubmit to the API service. I have attempted to use an Emitter Event to trans ...

Guide on linking action observables to emit values in sync before emitting a final value

If you're familiar with Redux-Observable, I have a method that accepts an observable as input and returns another observable with the following type signature: function (action$: Observable<Action>): Observable<Action>; When this method r ...

Error: Unable to attach the "identity" property as the object does not support extension

I encountered a simple TypeError while attempting to format my POST body. Below is the function I am using for handleSubmit : const handleSubmit = (values: any, formikHelpers: FormikHelpers<any>) => { const prepareBody = { ...values.customerC ...

Clear function of signature pad not working inside Bootstrap modal dialogue box

Currently, I'm working on implementing a signature pad dialogue box using Bootstrap modal. When the user clicks on the "Complete Activity" button, a dialog box should pop up with options for yes or no. If the user selects yes, another dialog box shoul ...

React JS displayed the string of /static/media/~ instead of rendering its markdown content

When I looked at the material UI blog template, I created my own blog app. I imported a markdown file using import post1 from './blog-posts/blog-post.1.md'; Next, I passed these properties to this component like so: <Markdown className=" ...

What is the correct way to use Observables in Angular to send an array from a Parent component to a Child

Initially, the task was to send JSON data from the parent component to the child component. However, loading the data through an HTTP request in the ngOnInit event posed a challenge as the data wasn't being transmitted to the child component. Here is ...

NextJS introduces a unique functionality to Typescript's non-null assertion behavior

As per the typescript definition, the use of the non-null assertion operator is not supposed to impact execution. However, I have encountered a scenario where it does. I have been struggling to replicate this issue in a simpler project. In my current proj ...

When working with data in Angular, make sure to use any[] instead of any in app.component.html and app.component.ts to avoid causing overload errors

I'm new to working with Angular, specifically using Angular 15. I have a Rest API response that I need to parse and display in the UI using Angular. To achieve this, I employed the use of HttpClient for making GET requests and parsing the responses. ...

Effortlessly passing props between components using React TypeScript 16.8 with the help

In this scenario, the component is loaded as one of the routes. I have defined the type of companyName as a string within the AppProps type and then specified the type to the component using <AppProps>. Later on, I used {companyName} in the HTML rend ...

Provide a string argument when instantiating an abstract class

I am searching for a method to assign a name string within a class and utilize it in the abstract class at the constructor level, without the need for a function. Opening up the constructor is not an option due to using typedi. You can access the playgrou ...

NextAuth - Error: The property 'accessToken' is not found in the 'Session' type

I'm encountering a problem when trying to deploy my application. Building it on my local machine works fine, but the build on GitHub is causing issues. Just so you know, I am using yarn and yarn build for this project. After doing some research, it ...

Navigating horizontally to find a particular element

I developed a unique Angular component resembling a tree structure. The design includes multiple branches and nodes in alternating colors, with the selected node marked by a blue dot. https://i.stack.imgur.com/fChWu.png Key features to note: The tree&ap ...

When compiling my TypeScript file, I encountered an error stating that a block-scoped variable cannot be redeclared

In my Visual Studio Code, I have written just one line of code in my ex1.ts file: let n: number = 10; Upon compiling using the command tsc ex1.ts, the compiler successfully generates the ex1.js file. However, VSC promptly displays an error in the .ts file ...

Attempting to retrieve data either by code or with a WHERE condition proves unsuccessful as the data retrieval process yields no results

Seeking assistance with my Angular project that is utilizing a Node.js server and MSSQL express. I am having trouble retrieving data using a WHERE condition in my code. Any help in identifying the missing piece or error would be appreciated. Thank you. // ...

Tips for fixing the error message "unable to access property 'property-name' of null"

I need assistance with retrieving data from a firebase database and storing it in an array using typescript. Below is the code snippet I am working with: export class ViewUserPage { public list = []; public ref = firebase.database().ref(); public ...

Elements can only be added to the array at the 0th index

In the process of developing a function, I encountered an issue where all elements added to the array were only stored in Array[0] of the rowData. The data is retrieved from a database. private createRowData() { var rowData:any[] = []; thi ...

How can I handle the different data type returned by ReactDom.render?

My main focus is on rendering Markdown. Additionally, I also need to parse HTML which is passed as a string. In this scenario, children represents the HTML passed as a string, while isParseRequired indicates if parsing is needed. import cx from 'clas ...

Enhancing Web Service Calls with Angular 2 - The Power of Chaining

I am currently facing an issue where I need to make multiple web service calls in a sequence, but the problem is that the second call is being made before the .subscribe function of the first call executes. This is causing delays in setting the value of th ...

Angular Material Table displaying real-time information

Recently, I've delved into Angular and have been experimenting with creating a dynamic table to showcase data. Currently, I have managed to get it partially working with static data. I drew inspiration from this particular example: https://stackblit ...

Creating Instances of Variables Within a Class

Currently, I am working on a project using Ionic and Angular. I have come across various ways of instantiating variables and I'm unsure about the implications of each method. Here are three scenarios that confuse me: export class someClass { myVaria ...