Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Typescript type that checks if a given key if passed to as a key to an object resolves to an array

This is kind of a theoretical question but I was wondering if we can build an input type that checks if a given enum key if passed to as a key to an object resolves to an array. I guess this is better explained with an example:

enum FormKeys {
  a = "a",
  b = "b",
}

interface IInitalFormValues {
  [FormKeys.a]: number
  [FormKeys.b]: string[]
}

const initialFormValues: IInitalFormValues = {
  [FormKeys.a]: 123,
  [FormKeys.b]: ["a"],
}

function onChange(input: FormKeys) {
  /*
    Error here since if we pass FormKeys.a then assigning an array to
    type number is an error
  */
  initialFormValues[input] = []
}

onChange(FormKeys.b)

What we can obviously do here is to change input: FormKeys to input: FormKeys.b but this solution does not scale well, is there a way to do it more generically?
Thanks

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

>Solution :

You can pick out the keys that are for array types by doing this (I picked this up here):

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

(That’s a bit tricky, full explanation below.) ArrayKeys<IInitialFormValues> would be just FormKeys.b, for instance, since that’s the only property with an array type. If you had more, ArrayKeys<IInitialFormValues> would be a union of them.

Then your input parameter’s type is ArrayKeys<IInitialFormValues>:

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

That way, this works:

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

but this fails:

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

Playground example


How that ArrayKeys works:

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

There are two stages to that:

  1. The {/*...*/} part maps each property of T into a new mapped type where the type of the property is the key itself or never. Suppose we had:

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

    Just the first part of ArrayKeys creates this anonymous type:

    {
        a: "a";
        b: "b";
        c: never;
    }
    
  2. Then the [keyof T] part at the end creates a union of the types of those properties, which in theory would be "a" | "b" | never, but never is always dropped from union types, so we end up with "a" | "b" — the keys of Example for properties with array types.

You can go a step further and build a type consisting of only the parts of Example that match that by using Pick<Example, ArrayKeys<Example>>, but you don’t need that for this specific purpose.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading