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);
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:
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.