Search code examples
typescriptunion-types

How to create a union type from nested arrays?


I have a constant object like this

const rack = {
    topShelf: {
        colors: ["red", "green"],
    },
    middleShelf: {
        colors: ["yellow", "blue"],
    },
    bottomShelf: {
        colors: ["orange", "purple"],
    },
}

And I want to create a union type like:

type Color = "red" | "green" | "yellow" | "blue" | "orange" | "purple"

The rack object is just an example. In a real application, the object has around 30 fields.

I know how to do it manually

type Color = 
    | typeof rack["topShelf"]["colors"][number] 
    | typeof rack["middleShelf"]["colors"][number]
    | typeof rack["bottomShelf"]["colors"][number]

But it is error-prone and I'm sure there is a way to get this union type inferred by Typescript.


Solution

  • Yes, it is possible.

    const rack = {
      topShelf: {
        colors: ["red", "green"],
      },
      middleShelf: {
        colors: ["yellow", "blue"],
      },
      bottomShelf: {
        colors: ["orange", "purple"],
      },
      extraDeepNestedProperty: {
        nestedProperty: {
          colors: ['rgb']
        }
      }
    } as const
    
    type Rack = typeof rack
    
    type IsNever<T> = [T] extends [never] ? true : false
    
    type Union<T, Cache extends string = never> =
      IsNever<keyof T> extends true
      ? Cache
      : {
        [Prop in keyof T]:
    
        T[Prop] extends ReadonlyArray<string>
        ? Cache | T[Prop][number]
        : Union<T[Prop], Cache>
      }[keyof T]
    
    type Result = Union<Rack>
    

    Treat Cache as a memoization.

    Union is a recursive type.

    Playground I made generic solution which works not only with 2 levels of nesting,