Home > Net >  How to build a Unique Record Type Reverser
How to build a Unique Record Type Reverser

Time:11-20

I want to build a type from type reverser as explained here Record Type Reverser

const VALUE = {
    field1: "fieldA",
    field2: "fieldB",
    field3: "fieldC"
} as const

type UReversed = UniqueReverser<typeof VALUE>
/*
should yield 
{
        fieldA: "field1"
        fieldB: "field2"
        fieldC: "field3"
}
*/

However this should only work if all literal values are unique!

Given the code below

const VALUE = {
    field1: "fieldA",
    field2: "fieldB",
    field3: "fieldA" // "fieldA" as a duplicate value here
} as const

type UReversed = UniqueReverser<typeof VALUE>
// should yield never

I want the type definition to yield yieldnever

Probably this could help Ensure the Uniqueness of Literal Values in a Record Type

CodePudding user response:

In this answer I explain how to detect if an object type has duplicate property value types (subject to caveats about optional properties, index signatures, unions, intersections, subtypes, and other complications). I won't duplicate this part of the answer here.

Anyway, one could change that solution a little to make something more general:

type IfUniqueProperties<T, Y = T, N = never> = unknown extends {
   [K in keyof T]-?: T[K] extends Omit<T, K>[Exclude<keyof T, K>] ? unknown : never
}[keyof T] ? N : Y

Here, IfUniqueProperties<T, Y, N> checks T to see if its properties are all unique; if so, it returns Y: if not, it returns N. All I did was change the never in the other solution to the generic N, and the T[keyof T] to the generic Y. I also made N default to never and Y default to T, since this is often what people want with these sort of "check" types. Let's make sure it works:

type U = IfUniqueProperties<{a: 1, b: 2}, "unique", "duplicates">
// type U = "unique"

type D = IfUniqueProperties<{a: 1, b: 1}, "unique", "duplicates">
// type D = "duplicates"

So we can write RecToDU<T> from the other answer in terms of IfUniqueProperties:

type RecToDU<T> = IfUniqueProperties<T, T[keyof T]>;
type ZY = RecToDU<{ a: "z", b: "y" }> // "z" | "y"
type NV = RecToDU<{ a: "z", b: "z" }> // never

We do want never so I left N as the default, but for Y you want T[keyof T] instead of T. So IfUniqueProperties is a generalized version of RecToDU.


Therefore, to answer this question, we can take the Reverser<T> type from this answer and write UniqueReverser<T> as follows:

type UniqueReverser<T extends Record<keyof T, PropertyKey>> =
   IfUniqueProperties<T, Reverser<T>>;

Let's test it:

const VALUES = {
   field1: "fieldA",
   field2: "fieldB",
   field3: "fieldC"
} as const

type UReversed = UniqueReverser<typeof VALUES>
/* type UReversed = {
 readonly fieldA: "field1";
 readonly fieldB: "field2";
 readonly fieldC: "field3"; */

and:

const VALUES = {
   field1: "fieldA",
   field2: "fieldB",
   field3: "fieldA"
} as const

type UReversed = UniqueReverser<typeof VALUES>
// type UReversed = never

Looks like what you wanted.

Playground link to code

  • Related