Sometimes we don't have the type, but we have the array value, and we want get the element type
const obj = [{size:"big", color:"blue"},{size:"small", color:"red"}]
To get the element type, we can do this, first assert the array as const, then
const colorAndSize = [{size:"big", color:"blue"},{size:"small", color:"red"}] as const
type ColorAndSize = typeof colorAndSize //[{ readonly size: "big"; readonly color: "blue";}, { readonly size: "small"; readonly color: "red";}]
type DeepMutable<T> = { -readonly [P in keyof T]: DeepMutable<T[P]> } // remove readonly modifier
type GetElementType<T extends readonly Record<string, unknown>[]> = DeepMutable<T[number]> // { size: "big"; color: "blue";} | { size: "small"; color: "red";}
const abc:GetElementType<ColorAndSize> = {size:"big", color:"blue"} // ok
const efg:GetElementType<ColorAndSize> = {size:"big", color:"red"} // error
we end up with a discriminating union and this may not be ideal because we cant mixed the types. eg we cannot have big and red.
here is how we compute the undiscriminating union
const colorAndSize = [{size:"big", color:"blue"},{size:"small", color:"red"}] as const
type ColorAndSize = typeof colorAndSize //[{ readonly size: "big"; readonly color: "blue";}, { readonly size: "small"; readonly color: "red";}]
type DeepMutable<T> = { -readonly [P in keyof T]: DeepMutable<T[P]> }; // remove readonly modifier
type GetUndiscriminatingElement <T extends Record<string,unknown>[], U extends T[number]=T[0]>= T extends [infer H, ...infer Rest]?
Rest extends []?{[key in keyof H]:U[key extends keyof U ? key:never]|H[key]}
: Rest extends Record<string,unknown>[]?GetUndiscriminatingElement<Rest,{[key in keyof H]:U[key extends keyof U ? key:never]|H[key]}>:"impossible route"
:"impossible route"
type A = GetUndiscriminatingElement<DeepMutable<ColorAndSize>> // { size: "big" | "small"; color: "blue" | "red";}
const a:A = {size:"big", color:"red"} // ok
limitation: the length of the array cannot exceed 999 because the max depth of TS recursion is only 1000
update: simpler and not bounded by recursion depth, thanks to @captainyossarian
const colorAndSize = [{size:"big", color:"blue"},{size:"small", color:"red"}] as const
type ColorAndSize = typeof colorAndSize //[{ readonly size: "big"; readonly color: "blue";}, { readonly size: "small"; readonly color: "red";}]
type GetUndiscriminatingElement <T extends readonly Record<string,unknown>[]> =
{ -readonly [P in keyof T]: T[P] } extends [infer H,...infer Rest]
? { -readonly [Key in keyof T[number]]: T[number][Key]}
: "you forgot to assert as const"
const A :GetUndiscriminatingElement<ColorAndSize> = {size:"big", color:"red"} // ok
Top comments (2)
It is possible to simplify it a bit:
very nice, this look much simpler!
I updated the post and added const assertion guarantee to it