There is a typeof
method for generating a type from a value, but it only converts the value directly into a type.
I want to use the value of one object to create type information for another object, as followings,
type Column = Readonly<{ type: "numeric" | "text"; name: string }>;
type TypeOfColumns<T extends Column[]> = {}; // ...
const columns: Column[] = [
{ type: "numeric", name: "id" },
{ type: "text", name: "firstName" },
];
// If I wrote the following,
type ColumnType = TypeOfColumns<typeof columns>;
// I want the type to be inferred as follows,
type NeedColumnType = { id: number; firstName: string };
I thought I could do it by using extends to read the values, but it did not work at all.
type Column = Readonly<{ type: "numeric" | "text"; name: string }>;
type TypeOfColumn<T extends Column> = {
[key in T["name"]]: T["type"] extends "numeric" ? number : string;
};
type TypeOfColumns<T extends Column[]> = {
[key in T[number]["name"]]: TypeOfColumn<T[number]>;
};
const columns: Column[] = [
{ type: "numeric", name: "id" },
{ type: "text", name: "firstName" },
];
type ColumnType = TypeOfColumn<typeof columns[0]>;
/* Output
type ColumnType = {
[x: string]: string;
}
*/
type ColumnsType = TypeOfColumns<typeof columns>;
/* Output
type ColumnsType = {
[x: string]: TypeOfColumn<Readonly<{
type: "numeric" | "text";
name: string;
}>>;
}
*/
CodePudding user response:
Let's start with columns
. The type of columns is currently Column[]
. Using typeof
on column
will therefore evaluate to Column[]
.
You instead want the compiler to infer the literal type by removing the type annotation. This needs to be done with as const
, so that the compiler infers the literal types instead of just string
. Optionally, you can use satisfies
readonly Column[]
to validate the type.
const columns = [
{ type: "numeric", name: "id" },
{ type: "text", name: "firstName" },
] as const satisfies readonly Column[]
The constraint of TypeOfColumns
now has to change to accept readonly
arrays. The mapping logic inside the type was also needs to change. We map over T[number]
instead of T[number]["name"]
. Each union member K
can be used to extract the property name K["name"]
and the corresponding type K["type"]
.
type TypeOfColumns<T extends readonly Column[]> = {
[K in T[number] as K["name"]]: K["type"] extends "numeric"
? number
: string;
};
type ColumnType = TypeOfColumn<typeof columns[0]>;
/*
type ColumnType = {
id: number;
}
*/
type ColumnsType = TypeOfColumns<typeof columns>;
/*
type ColumnsType = {
id: number;
firstName: string;
}
*/