2

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.

2 Answers 2

3

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,

Sign up to request clarification or add additional context in comments.

Comments

0

I have ended up with this solution:

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

const colors = Object.values(rack).map(shelf => shelf.colors).flat()

type Color = typeof colors[number]

I will need "colors" as a runtime value later so in my specific case this solution is the best one.

But the solution from @captain-yossarian is generic so I marked it as an answer.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.