Is it possible to access the instanced value of an object property in a TypeScript interface

Consider the following type for a column within a data table:

interface Column<T> {
  key: keyof T;
  title: string;
  format?: (value: T[keyof T], record: T) => string | number;
}

T is the data type of the table records, and the value to be displayed in the column is indexed by the key property. A format function would perform custom formatting or other processing for a specific column to be shown in the table, and so accepts the value of the column, which is T[keyof T], and the record itself.

The issue this presents is that T[keyof T] represents a union type of all data types contained within in type T.

If, for example, T were a Person with the type:

interface Person {
  name: string;
  weight: number;
  birthday: Date;
}

then T[keyof T] would equal string | number | Date. But, when an object is instantiated with a format function, the specific type of the value cannot be determined.

const column: Column<Person> = {
  key: 'birthday',
  title: "Birth Date",
  render: (value) => value.getFullYear();
}

This would throw an error because TypeScript cannot be certain that value is in fact of type Date and so contains the getFullYear() method.

I know that it’s possible to add a second generic argument to the type, like so:

export interface Column<T, K extends keyof T> {
  key: K;
  title: string;
  format?: (value: T[K], record: T) => string | number;
}

In this case, it is possible to type the previous example correctly by specifying the key name:

const column: Column<Person, 'birthday'> = {
  key: 'birthday',
  title: "Birth Date",
  format: (value) => value.getFullYear()
}

But this is not practically useful, as almost all usages of a Column would exist in an Array with a single type declaration.

Is it somehow possible to access the specific instanced value of the key property in an interface/type, or create an auxiliary type that does so, such that you can define a property which is the type of the specific property indexed by that exact key?

I know I can simply resolve these errors with type assertions, but it bothers me that I can’t find a cleaner way to do it.

>Solution :

This approach uses a ‘throwaway’ Mapped type to create a union of all valid Column structures, but where each one is key-specific. You can hover over value in the Typescript Playground to prove it…

interface Person {
  name: string;
  weight: number;
  birthday: Date;
}

type Column<T> = {
  [K in keyof T]: {
    key: K;
    title: string;
    format?: (value: T[K], record: T) => string | number;
  };
}[keyof T];

const column: Column<Person> = {
  key: "birthday",
  title: "The day you were born",
  format: (value, record) => "some formatted string",
};

Leave a Reply