Dan
Dan

Reputation: 21

How to exhaustive check the elements in an array in typescript?

Given that I can use a Record to make me not forget to type all options in an object

type Country = "uk" | "france" | "india";

export const data: Record<Country, boolean> = {
  uk: true,
  france: true,
  // complains that india is not present, excellent!
};

how can I make so it complains the same way for arrays?

export const data = [
  {value: "uk"},
  {value: "france"},
  // how to make typescript complain here that I forgot to add {value: "india"}?
];

Upvotes: 1

Views: 626

Answers (3)

geoffrey
geoffrey

Reputation: 2444

I know it's ugly, but it's not factorial complexity and it does not require a helper function.

type Country = "uk" | "france" | "india";

const data = [
    { value: 'uk' },
    { value: 'france' },
    { value: 'india' }
] as const;

type test = IsExhaustive<typeof data>

type IsExhaustive<
    T extends Country extends Values ? unknown : { error: [Country, 'does not extend', Values] },
    Values =  keyof {
        [K in keyof T as T[K] extends {value: PropertyKey} ? T[K]['value'] : never]: never
    }
> = T

Upvotes: 0

arslivinski
arslivinski

Reputation: 61

Based on this answer, I think that this solves your problem:

type Country = "uk" | "france" | "india";

type MapOfKeysOf<U extends string> = {
    [key in U]: MapOfKeysOf<Exclude<U, key>>;
}

type ExhaustiveArrayOfObjects<Keys extends {[key: string]: {}}, T = {}, KeyName extends string = "value"> = {} extends Keys ? [] : {
    [key in keyof Keys]: [T & Record<KeyName, key>, ...ExhaustiveArrayOfObjects<Keys[key], T, KeyName>];
}[keyof Keys]

export const data: ExhaustiveArrayOfObjects<MapOfKeysOf<Country>> = [
    {
        value: 'uk',
    },
    {
        value: 'france',
    },
    {
        value: 'india',
    }
]

If you want, you can pass an extra type to extends or event customize the prop that will hold de value

type Country = "uk" | "france" | "india";

type MapOfKeysOf<U extends string> = {
    [key in U]: MapOfKeysOf<Exclude<U, key>>;
}

type ExhaustiveArrayOfObjects<Keys extends {[key: string]: {}}, T = {}, KeyName extends string = "value"> = {} extends Keys ? [] : {
    [key in keyof Keys]: [T & Record<KeyName, key>, ...ExhaustiveArrayOfObjects<Keys[key], T, KeyName>];
}[keyof Keys]

type Option = {
    label: string,
    id: Country
}

const data: ExhaustiveArrayOfObjects<MapOfKeysOf<Country>, Option, "id"> = [
    {
        label: "United Kingdom",
        id: 'uk',
    },
    {
        label: "France",
        id: 'france',
    },
    {
        label: "India",
        id: 'india',
    }
]

const option: Option = data[0]

You can check more on this playgroud

Upvotes: 2

manwingbb
manwingbb

Reputation: 51

I am using some utility functions from a library but the it is total achievable without using one. I actually got some help from stack overflow in order to come up with this solution haha... Credits to caTS Related question

import { List, Union } from "ts-toolbelt";

type GetData<TCountries extends string[]> = List.Length<TCountries> extends 0
  ? []
  : List.Append<GetData<List.Pop<TCountries>>, {
    value: List.Last<TCountries>
  }>;

export const data : GetData<Union.ListOf<Country>> = [
  {value: "uk"},
  {value: "france"},
  // how to make typescript complain here that I forgot to add {value: "india"}?
];

Upvotes: 0

Related Questions