Home > OS >  Typescript generics provide available words
Typescript generics provide available words

Time:07-29

i try to get a list of available id's from a object array. After search, trail and error i hope i can find some help.

I have a array which contains a objects, like this example:

const myValues = [
    {id: 'abc', label: 'anyLabelForAbc'},
    {id: 'xyz', label: 'anyLabelForXyz'},
    {id: 'foo', label: 'anyLabelForFoo'},
    {id: 'bar', label: 'anyLabelForBar'},
]

In the next step i would get all available id's like 'abc' | 'xyz' | 'foo' | 'bar' from a generic type like

const availableIds: AvailableIds<typeof myValues > = 'abc' //'abc' | 'xyz' | 'foo' | 'bar'

I have try different ways, but none of my attempts works as expected. This is my code now which doesnt works

type Item = { id: string; label: string };
type Index<T> = T extends number ? T : never;
type Ids<T extends Item []> = T[Index<T>]['id'];

const items: Item[] = {
    {id: 'abc', label: 'anyLabelForAbc'},
    {id: 'xyz', label: 'anyLabelForXyz'},
    {id: 'foo', label: 'anyLabelForFoo'},
    {id: 'bar', label: 'anyLabelForBar'},
}
const ids: Ids<typeof items> = ''; // geting nothing suggested

Thank you

CodePudding user response:

The first part is to add as const to items in order to preserve the literal types for id, other wise they will just be widened to string.

Then you can just use a index type query to get the type of id from the items in items:

const items = [
  { id: 'abc', label: 'anyLabelForAbc' },
  { id: 'xyz', label: 'anyLabelForXyz' },
  { id: 'foo', label: 'anyLabelForFoo' },
  { id: 'bar', label: 'anyLabelForBar' },
] as const

type AvailableIds = typeof items[number]['id']
const ids: AvailableIds[] = items.map(o => o.id);

Playground Link

CodePudding user response:

By using a const assertion, you can get the compiler to infer literal types in places where it normally won't (e.g. the compiler will only infer strings from property values in objects, not string literals).

You can combine this concept with a constrained identity function to extract the types and data that you're after:

TS Playground

type Item = { id: string; label: string };

function createItems <T extends readonly Item[]>(items: T): T {
  return items;
};

const items = createItems([
  {id: 'abc', label: 'anyLabelForAbc'},
  {id: 'xyz', label: 'anyLabelForXyz'},
  {id: 'foo', label: 'anyLabelForFoo'},
  {id: 'bar', label: 'anyLabelForBar'},
] as const); /*
  ^^^^^^^^
const assertion */

const ids = items.map(({id}) => id);
    //^? const ids: ("abc" | "xyz" | "foo" | "bar")[]

Using a constrained identity function can help you avoid making mistakes when creating your data because it ensures that the value that you input is assignable to the type constraint:

const invalidItems = createItems([
  {id: 'abc', label: 'abc'},
  {id: 'xyz'},
] as const); // Compiler error: ...Property 'label' is missing in type '{ readonly id: "xyz"; }' but required in type 'Item'.(2345)

And if you don't use a constraint, then the const assertion doesn't help you, and you'll just end up with data that's not the required type.

  • Related