When T
does not have any duplicate property values, the desired outcome for RecToDU<T>
is to encompass all its property value types as a union; otherwise, it should be set to never
. The procedure to compute the union of property value types in T
involves indexing into T
with the union of its keys: T[keyof T]
(view Q/A here)
.
The definition for RecToDU<T>
can take shape as a conditional type structured like this:
type RecToDU<T> = XXX extends YYY ? T[keyof T] : never
or perhaps
type RecToDU<T> = XXX extends YYY ? never : T[keyof T]
. Now, let's figure out what
XXX
and
YYY
will be.
One method to approach this is:
type RecToDU<T> = unknown extends {
[K in keyof T]-?: T[K] extends Omit<T, K>[Exclude<keyof T, K>] ? unknown : never
}[keyof T] ? never : T[keyof T]
Explaining further:
{ [K in keyof T]-?: T[K] extends Omit<T, K>[Exclude<keyof T, K>] ? unknown : never }
This process involves mapping over each property key K
in T
, and comparing each property value T[K]
to
Omit<T, K>[Exclude<keyof T, K>]
. In simpler terms,
Omit<T, K>[Exclude<keyof T, K>]
establishes the union of all property value types in
T
except at the specified key
K
.
For instance, if T
is {a: 0, b: 1, c: 2}
and K
is "a"
, then T[K]
becomes 0
. Omit<T, K>
would be {b: 1, c: 2}
, and Exclude<keyof T, K>
translates to "b" | "c"
, thus resulting in
Omit<T, K>[Exclude<keyof T, K>]
equating to
1 | 2
. Hence, when we compare
0 extends 1 | 2 ? ...
, we assess whether the property at that particular key
K
appears elsewhere in the object or not.
If
T[K] extends Omit<T, K>[Exclude<keyof T, K>]
evaluates to true, it implies duplication of property values across different properties. On the other hand, false indicates uniqueness for the respective property. Depending on this evaluation, either
the unknown
type (for duplicates) or
the never
type (for unique values) is returned, shaping the resultant object accordingly.
With rigorous testing and consideration of specific use cases, adjustments may be required in defining RecToDu<T>
based on varying object structures and scenarios involving optional properties, index signatures, and data types not limited to literals alone.
Test code in TypeScript Playground