Typescript Get Undiscriminating Union Object Type from Array of Object Literal
Tags: webdev, typescript, programming, tutorial
Published: 2022-06-22 06:47:46

Sometimes we don't have the type, but we have the array value, and we want get the element type

```ts
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

```ts
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
```

[playground](https://www.typescriptlang.org/play?#code/MYewdgzgLgBKA2IBOBBMATAygSwF4FMYBeGAbQG8I98AuAIgCNsBzOgGjhESXofgFd8dAL5tK1ehAC2AQ3jx2nbvST50IgLowZETpCgBYAFDGoATwAOhAMJdkaLNWIxzVkADMl9jDgIwA9P4UMKoy6ODwZjBUBDQwjCx0ANwh+GERUQjIcYwCQkmiMOSp6WCR0RLx0nIKKaHhZZl2PPGq6gUaxqaWhAAi+PgWALL8UDJ8+AA8ACoAfM7FALT1GWQACjDYYDAA1vhmHjDTGnH9gyNjEzOkaxrzwgH+qVIgAG6EK40wL+jY7tj4JBdIyuQgAcXwUAAovB8FJ8GAoNMejMYPgAB5QBHoXSfcoAJXwoCQ6Em0CQW2YHH4YB2YBAAHcwLNSHdnGdhqNxrDrmB+FIGIC2YEihVYvEmKwUlkWrlBMkHgAfUUxWhVWTyZJeWVtBXA0D6bQMYA0CHQ2HwxHIqyTWzcBy+fDzEjiWIJVgcGW8PIiR4wEA7fXgaBo9zMU2QmFwhFIlF27yOAjOoqq3iJT3NFRqX0iwFIZDGIA)

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
```ts
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

```

[playground](https://www.typescriptlang.org/play?#code/MYewdgzgLgBKA2IBOBBMATAygSwF4FMYBeGAbQG8I98AuAIgCNsBzOgGjhESXofgFd8dAL5tK1ehAC2AQ3jx2nbvST50IgLowZETpCgBYAFDGoATwAOhAMJdkaLNWIxzVkADMl9jDgIwA9P4UMKoy6ODwZjBUBDQwjCx0ANwh+GERUQjIcYwCQkmiMOSp6WCR0RLx0nIKKaHhZZl2PPGq6gUaxqaWhAAi+PgWALL8UDJ8+AA8ACoAfM7FALT1GWQACjDYYDAA1vhmHjDTGnH9gyNjEzOkaxrzwimBqVIgAG6EK40wL+jY7tj4JBdIyuQgAcXwUAAqhhsBBgEhsFItjIoFtmABReD4KT4MCwGYwfAADygePQugASvhQEh0JNoIiwMw2PwwDswCAAO5gWakDQcKFE0nk3TTUhgfhSBiAjREcUABjuJGmwrJGF0pC27kBMAAEhwAHTG7W66nQDQAfmMMBg5tgJPVFLIVvIpD2US2u32hz1Jyh7v2atF3oOniFltDNDA+HeSA0AB89YGzBphDaYHF7cGNXaach6Yz0az2ZyeXyrRDobD4YjkWBUeisTi8VBJvaxCnNtsPb7-V3HSHe+GYJGPdHY7Kkym07N6EiLCAIFQJiEQKMhMZ51JF8vsKukOuyXRgaCYChnDAqzDfrWkSi0czm7j8ZMzsNRuNsZNbNwHL58FmeYnmKGJaHiJhWBgBMqlkeRki8FpckEOhoNaNRknTEwjFAfRtBoC8SHEWIElYDgsh4Og2hEAJ-BgEAdmBYwgA)


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

```ts
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](https://www.typescriptlang.org/play?#code/MYewdgzgLgBKA2IBOBBMATAygSwF4FMYBeGAbQG8I98AuAIgCNsBzOgGjhESXofgFd8dAL5tK1ehAC2AQ3jx2nbvST50IgLowZETpCgBYAFDGoATwAOhAMJdkaLNWIxzVkADMl9jDgIwA9P4UMKoy6ODwZjBUBDQwjCx0ANwh+GERUQjIcYwCQkmiMOSp6WCR0RLx0nIKKaHhZZl2PPGq6gUaxqaWhADi+FAAqhjYEMBI2FLYYDJQ08wAovD4UvhgUABMMAA8ACow+AAeUGvouvUZMABK+KBI6NvQE2DMbPxgANZgIADuYAB8pA0-2cxmKAFoLo0yAAFGDTGAffBmDwwXYaOK7UgwrTCA7HU66UjTdz4JAwAASbAAdLSSWTrvhoFpjDAYAB+IowSFpBrlUgAaWR8LAiORqKxYH4UgYZI0GLRpClMrlguRGjxrJgORR-Bg7mQzBAsCgIG0EAgZNgOj00DoXSMoH0MBQ2v6QxGYwmUxmcxeSxWa0221s3AcvnwIJI4liCVYHCyPDobREAX8MBAHwdQA)