I would like to flatten down an object. This works well on the js side, but ts is a bit tricky to convert the object correctly.
I created a js method which converts a objects like this:
const from = {
red: '#red-val',
green: {
200: '#green-200-val',
300: '#green-300-val',
DEFAULT: '#green-default-val',
},
}
to this:
const to = {
red: '#red-val',
green: '#green-default-val',
'green-200': '#green-200-val',
'green-300': '#green-300-val',
}
On the js side, this works fine, but obviously Typescript doesn't understand this conversion, which is why I need to create a custom type for it.
So I created a couple of generic types, which are a bit hacky, but seems to work at first glance.
type HEX_VAL = `#${string}`;
type Props = {
[K in string]:
| HEX_VAL
| {
[IK in string]: HEX_VAL;
};
};
type ConvertChildKey<K extends string, P extends string> = K extends 'DEFAULT'
? P
: `${P}-${K}`;
type FlatColorKeys<P extends Props> = {
[K in keyof P]: P[K] extends Props
? //@ts-ignore
ConvertChildKey<keyof P[K], K extends string ? K : never>
: K;
}[keyof P];
type FlatColorsSettingsT<T extends Props> = Record<FlatColorKeys<T>, HEX_VAL>;
But here is the problem:
// this works fine. Hovering over this type, shows the correct result.
type ExampleConversion = FlatColorsSettingsT<{
red: '#red-val';
green: {
200: '#green-200-val';
300: '#green-300-val';
DEFAULT: '#default-color';
};
blue: {
300: '#sddsf';
};
}>;
// does not work fine. Hovering over this const just shows the generic type and not the result of it.
const ExampleMethodConversion = flatColorsSettings({
red: '#red-val',
green: {
200: '#green-200-val',
300: '#green-300-val',
},
});
Link to Playground. It includes also the js part.
Is there a better way to achieve the desired result? And to get the converted type and not the generic type when executing the method?
Thanks!
CodePudding user response:
This is one more case where the tooling surrounding TypeScript is lazy and does not show the fully expanded type. The output type is correct, but not really useful to a human reader. We can resolve this problem by using the Expand
utility type which forces a re-evaluation of the object type.
type FlatColorsSettingsT<T extends Props> = Record<FlatColorKeys<T>, HEX_VAL>;
type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
export const flatColorsSettings = <P extends Props>(
colors: P
): Expand<FlatColorsSettingsT<P>> =>
Object.assign(
/* ... */
);
Which leads to a readable type hint.
const ExampleMethodConversion = flatColorsSettings({ /* ... */ });
/*
const ExampleMethodConversion: {
red: `#${string}`;
"green-200": `#${string}`;
"green-300": `#${string}`;
}
*/