I have this object describing Legend for Chart component
export const MONTHLY_ANNUAL_LEGEND: Legend = [
{
title: 'Monthly',
key: 'monthly',
subItems: [
{
type: 'bar',
key: 'progress'
},
{
type: 'line',
key: 'goal'
},
],
},
{
title: 'Annual',
key: 'annual',
subItems: [
{
type: 'line',
key: 'progress'
},
{
type: 'line',
key: 'goal'
},
],
},
] as const;
And wanted to have a resulted type:
type LegendKeys = 'monthly_progress' | 'monthly_goal' | 'annual_progress' | 'annual_goal';
Which is a combination of MONTHLY_ANNUAL_LEGEND[number]['key']
and MONTHLY_ANNUAL_LEGEND[number]['subItems'][number]['key']
The first thing I've found is to make a builder-function which will return you a type, applicable for union type, but didn't found a solution to type subItems
function makeLegendItem<Key extends string>(item: LegendItem<Key>): LegendItem<Key> {
return item;
}
CodePudding user response:
You can just use template literals:
type Legend = typeof MONTHLY_ANNUAL_LEGEND[number];
type Items =`${Legend["key"]}_${Legend["subItems"][number]["key"]}`;
CodePudding user response:
Using a recursive type which goes through the object would look like this:
type LegendKeys<T extends readonly any[]> = T extends readonly (infer I)[]
? I extends Record<"key", string> & Record<"subItems", readonly any[]>
? `${I["key"]}_${LegendKeys<I["subItems"]>}`
: I extends {key: string}
? I["key"]
: never
: never
type Test = LegendKeys<typeof MONTHLY_ANNUAL_LEGEND>
// 'monthly_progress' | 'monthly_goal' | 'annual_progress' | 'annual_goal'
This has the benefit of working for any depth. So a subItem
could also have its own subItems
and the type would still work.
I had to specify readonly
at multiple points since you used as const
at the variable initialization.