Home > Software engineering >  How to create a type helper that uses information about an object to infer a new type
How to create a type helper that uses information about an object to infer a new type

Time:01-13

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;
    }
*/

Playground

  • Related