To simplify the process of converting a type like
{a: "A", b: "B"}
to its inverse,
{A: "a", B: "b"}
, TypeScript 4.1 introduces the use of mapped type
as
clauses as shown in
microsoft/TypeScript#40336:
// TS4.1+
type Invert<T extends Record<keyof T, PropertyKey>> = { [K in keyof T as T[K]]: K };
For TypeScript versions before 4.1, a similar effect can be achieved using mapped conditional types:
// TS4.0-
type Invert<T extends Record<keyof T, PropertyKey>> =
{ [K in T[keyof T]]: { [P in keyof T]: K extends T[P] ? P : never }[keyof T] }
In both cases, the goal is for keyTextBijection
to take an object of type T
and return an object of type T & Invert<T>
. To make this merge look cleaner, the no-op mapped type Id
is defined to achieve this:
type Id<T> = T extends infer U ? { [K in keyof U]: U[K] } : any;
The function signature for keyTextBijection
is setup with a single overload call that allows for greater flexibility with type safety using any
.
A test case is provided to validate the implementation:
const bij = keyTextBijection({ a: "A", b: "B", c: "C" });
/* const bij: {
a: "A";
b: "B";
c: "C";
A: "a";
B: "b";
C: "c";
} */
console.log(bij);
/* {
"a": "A",
"A": "a",
"b": "B",
"B": "b",
"c": "C",
"C": "c"
} */
The results seem accurate based on the values passed in.
Playground link to code