2

I'm trying to create a typescript type that looks like this:

type Colors = {
  'blue': string;
  'blue20': string;
  'blue40': string;
  'blue60': string;
  'blue80': string;
  'green': string;
  'green20': string;
  'green40': string;
  'green60': string;
  'green80': string;
}

There's more colors and color variants but this is a simplified version. I'm trying to generate this like this:

type Variations = '' | 20 | 40 | 60 | 80;
type TAllColors = {
  [P in Variations as `blue${P}`]: string;
  [P in Variations as `green${P}`]: string;
};

However, I'm getting an error:

Conversion of type 'boolean' to type 'green${any}' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.ts(2352)

It works if I just have the blue line inside but when I add the line for green it no longer works and gives me that error. Does anyone know how to accomplish what I'm trying?

MarksCode
  • 8,074
  • 15
  • 64
  • 133

2 Answers2

2

Maybe try something like this

type Variations = '' | 20 | 40 | 60 | 80;
type Variations2 = '' | 25 | 45 | 65 | 85;
type TAllColors = {
  [P in Variations as `blue${P}`]: string;
}&
{
  [P in Variations2 as `green${P}`]: string;
}
Nadia Chibrikova
  • 4,916
  • 1
  • 15
  • 17
1

This syntax is invalid.

You need just replace blue and green with a union of all allowed colors:

type ColorNames = 'blue' | 'green'

type Variations = '' | 20 | 40 | 60 | 80;

type TAllColors<Color extends string> = {
    [P in Variations as `${Color}${P}`]: string;
};

type Result = TAllColors<ColorNames>

type Colors = {
    'blue': string;
    'blue20': string;
    'blue40': string;
    'blue60': string;
    'blue80': string;
    'green': string;
    'green20': string;
    'green40': string;
    'green60': string;
    'green80': string;
}

// true
type IsCorrect =
    Colors extends Result
    ? Result extends Colors
    ? true
    : false
    : false

Playground

What if green and blue have different variations ? Then you need to create appropriate map data structure and iterate over newly created map and variations.


type ColorNames = 'blue' | 'green'

type VariationsBlue = '' | 10 | 20 | 30 | 40
type VariationsGreen = '' | 20 | 40 | 60 | 80;

type VariationsMap = {
    blue: '' | 10 | 20 | 30 | 40;
    green: '' | 20 | 40 | 60 | 80;
}

type NameConvert<T, V> =
    T extends string
    ? V extends string | number
    ? `${T}${V}`
    : never
    : never


type TAllColors<Variations> = {
    [V in keyof Variations]: {
        [P in V as NameConvert<P, Variations[V]>]: string
    }
}

type TransitionResult = TAllColors<VariationsMap>

Sush iteration produces two levels of nested properties.

type TransitionResult = {
    blue: {
        blue: string;
        blue10: string;
        blue20: string;
        blue30: string;
        blue40: string;
    };
    green: {
        green: string;
        green20: string;
        green40: string;
        green60: string;
        green80: string;
    };
}

We need somehow to obtain all values from above object and merge them. Retrieving all values is relatively easy:

type Values<T> = T[keyof T]

type TransitionResult = Values<{
    blue: {
        blue: string;
        blue10: string;
        blue20: string;
        blue30: string;
        blue40: string;
    };
    green: {
        green: string;
        green20: string;
        green40: string;
        green60: string;
        green80: string;
    };
}>

As you might have noticed we ended up with union of all values whereas we need an intersection.

type Values<T> = T[keyof T]

// credits goes to https://stackoverflow.com/a/50375286
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
    k: infer I
) => void
    ? I
    : never;

type Result = UnionToIntersection<Values<{
    blue: {
        blue: string;
        blue10: string;
        blue20: string;
        blue30: string;
        blue40: string;
    };
    green: {
        green: string;
        green20: string;
        green40: string;
        green60: string;
        green80: string;
    };
}>>

Whole code:


type ColorNames = 'blue' | 'green'

type VariationsBlue = '' | 10 | 20 | 30 | 40
type VariationsGreen = '' | 20 | 40 | 60 | 80;

type VariationsMap = {
    blue: '' | 10 | 20 | 30 | 40;
    green: '' | 20 | 40 | 60 | 80;
}

type NameConvert<T, V> =
    T extends string
    ? V extends string | number
    ? `${T}${V}`
    : never
    : never


type TAllColors<Variations> = {
    [V in keyof Variations]: {
        [P in V as NameConvert<P, Variations[V]>]: string
    }
}

type Values<T> = T[keyof T]

// credits goes to https://stackoverflow.com/a/50375286
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
    k: infer I
) => void
    ? I
    : never;

type Result = UnionToIntersection<Values<TAllColors<VariationsMap>>>

Playground