Typescript Get Undiscriminating Union Object Type from Array of Object Literal

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

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Acid Coder

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