This is actually not that hard. All you need is a mapped type with keys remapped to the type of the name
property of the tuple members (which can be done very nicely with key remapping since 4.1) and values to the type
property of corresponding members.
First, you need to let the compiler know the properties
array is actually a tuple with an as const
assertion:
const properties = [
{ name: 'name', type: '' },
{ name: 'age', type: 0 },
{ name: 'sex', type: ['m', 'f'] as const },
{ name: 'username', type: '' }
] as const;
Next, let's make the type reusable by defining a generic type able to operate on arbitrary properties of the members of the tuple. First, we need to extract indices to be able to map
members to properties one-to-one. This is done via Exclude<keyof T, keyof readonly any[]>
(leaves indices only).
We can use the resulting union of indices to do the mapping:
type TupleToProps<T extends readonly any[], VP extends keyof T[number], VV extends keyof T[number]> = {
[ P in Exclude<keyof T, keyof readonly any[]> as T[P][VP] & string ] : T[P][VV]
};
VP
and VV
ensure the utility type can work with any homogenous tuple of objects and & string
leaves only string-compatible properties. This already yields us a pretty nice result:
type PersonTest = TupleToProps<typeof properties, "name", "type">;
/**
* type PersonTest = {
* name: "";
* age: 0;
* sex: readonly ["m", "f"];
* username: "";
* }
*/
After that, this is only a matter of cosmetic changes: extracting values from tuple-like types and widening literal ones (courtesy of this and this Q&A with a small upgrade to exclude keys that should not be widened):
type ToPrimitive<T> =
T extends string ? string
: T extends number ? number
: T extends boolean ? boolean
: T;
// mapped types which will preserve keys with more wide value types
type Widen<O, E = never> = {
[K in keyof O]: K extends E ? O[K] : ToPrimitive<O[K]>
}
type TupleToPropsTwo<T extends readonly any[], VP extends keyof T[number], VV extends keyof T[number], E extends T[number][VP]> = Widen<{
[ P in Exclude<keyof T, keyof readonly any[]> as T[P][VP] & string ] : T[P][VV] extends readonly any[] ? T[P][VV][number] : T[P][VV]
}, E>;
type PersonTest2 = TupleToPropsTwo<typeof properties, "name", "type", "sex">;
/**
* type PersonTest2 = {
* name: string;
* age: number;
* sex: "m" | "f";
* username: string;
* }
*/
Playground
properties
array into a type declaration and put it in a .d.ts file. – Cherubini