Typescript Get Undiscriminating Union Object Type from Array of Object Literal

Acid Coder
2 min readJun 22, 2022

Sometimes we don’t have the type, but we have the array value, and we want to 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 consttype 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 modifiertype 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”} // okconst efg:GetElementType<ColorAndSize> = {size:”big”, color:”red”} // error

playground

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 consttype 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 modifiertype 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

playground

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

playground

--

--

Acid Coder

Typescript Zombie. Youtube Pikachu On Acid. (Unrelated to programming but by watching it you become a good developer overnight)