Search code examples
typescriptmapped-types

How to build a Unique Record Type Reverser


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


Solution

  • 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