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.
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,