Home > Software engineering >  TS : How to create a type factory using a string to generate the keys
TS : How to create a type factory using a string to generate the keys

Time:05-18

I am trying to create a way to easily generate a type that defines a multitude of key values association with a specific name pattern in the keys.

I have a specific type that is very repetitive, as for any metrics (working on analytics), I have a 4 keys associated.

For example, for the firstPageVisible metric, I will have firstPageVisibleMetrics, firstPageVisibleCount, firstPageVisibleMetricsPerDevices, firstPageVisibleCountPerDevices.

But as I have many metrics, I would like to have some sort of factory to make is easier to read.

I was imagining something like :

type DetailedMetric<Type> = (
    name: string,
) => {
    [`${name}Metrics`]?: Type;
    [`${name}Count`]?: number;
    [`${name}MetricsPerDevices`]?: PerDeviceData<Type>;
    [`${name}CountPerDevices`]?: PerDeviceData<number>;
};

But I am having the error : A computed property name in a type literal must refer to an expression whose type is a literal type or a 'unique symbol' type..

And I cannot find a satisfying way to resolve this.

In the end, I would like to have something that looks like this (or similar) :


type StoryDataType = {
    _id: string;
    ...;
} & DetailedMetric("firstPageVisible")<number> &
    DetailedMetric("metric2")<number> &
    DetailedMetric("metric3")<number> &
    DetailedMetric("metric4")<number> &
    ...;

CodePudding user response:

You can define DetailedMetrics like this:

type DetailedMetric<Type, Name extends string> =  {
    [K in `${Name}Metrics`]?: Type 
} & {
    [K in `${Name}Count`]?: number 
} & {
    [K in `${Name}MetricsPerDevices`]?: PerDeviceData<Type> 
} & {
    [K in `${Name}CountPerDevices`]?: PerDeviceData<number> 
}

Playground

CodePudding user response:

You could map over a base type and use a relatively new TypeScript feature called Key Remapping allowing you to compute a property key as you are trying to do`.

type Base<T> = {
    Metrics?: T;
    Count?: number;
    MetricsPerDevices?: number;
    CountPerDevices?: number;
}

type DetailedMetric<Type> = <N extends string>(
    name: N,
) => {
    // Remap the name to be our generic `N`.
    [Key in keyof Base<Type> as `${N}${Key}`]: Base<Type>[Key];
};

This way you can define new properties in our Base type and have them automatically carried over.

Here's a playground link

  • Related