feihcsim
feihcsim

Reputation: 1552

How can I get the inverse of a TypeScript mapped type?

I am looking to get the "inverse" of a TypeScript mapped type (whose properties are strictly strings, so as to be "inversible"). To illustrate my desired result, I need a generic type

type Inverse<M> = ...

to be able to transform

type MappedType = {
  key1: 'value1'
  key2: 'value2'
};

into

/**
 * {
 *   value1: 'key1';
 *   value2: 'key2';
 * }
 */
type MappedTypeInverse = Inverse<MappedType>

I've tried a couple things already.. but to no avail:

type Inverse<M> = M extends Record<infer O, infer T> ? Record<T, O> : never;

/**
 * type MappedTypeInverse = {
 *   value1: 'key1' | 'key2'
 *   value2: 'key2' | 'key2'
 * }
 */
type MappedTypeInverse = Inverse<MappedType>
type InverseValue<M extends Record<any, any>, V extends M[keyof M]> = V extends M[infer K] ? K : never;

/**
 * type MappedTypeInverseValue = unknown // expecting 'key1'
 */
type MappedTypeInverseValue = InverseValue<MappedType, 'value1'>

Is this even possible? Any help would be appreciated!

Upvotes: 4

Views: 2602

Answers (4)

pom421
pom421

Reputation: 1964

type Foo = {
  key1: "val1";
  key2: "val2"
}

type Inverse<T extends Record<string, string>> = {
  [K in keyof T as T[K]]: K
}

type Bar = Inverse<Foo>
// type Bar = { val1: "key1"; val2: "key2"; }

Upvotes: 1

Meir
Meir

Reputation: 14395

You can use mapped types with the as operator.

See playground example.

type Inverse<T> = {[K in keyof T as (T[K] & (string | number))]: K};

Upvotes: 2

ford04
ford04

Reputation: 74770

Here is a lean alternative (in addition to Patrick Robert's good solution):

type KeyFromVal<T, V> = {
  [K in keyof T]: V extends T[K] ? K : never
}[keyof T];

// we assume the type to be an object literal with string values
// , should also work with number or symbol
type Inverse<M extends Record<string, string>> = {
  [K in M[keyof M]]: KeyFromVal<M, K>
};

type MappedType = {
  key1: 'value1'
  key2: 'value2'
};

type MappedTypeInverse = Inverse<MappedType> // { value1: "key1"; value2: "key2"; }

Upvotes: 5

Patrick Roberts
Patrick Roberts

Reputation: 51957

Here's how you can accomplish this. It borrows some "evil magic" from this answer to convert a union to an intersection at an intermediate step in the process:

type MappedType = {
    key1: 'value1';
    key2: 'value2';
};

type Intermediate<R extends Record<string, string>> =
    R extends Record<infer K, string>
    ? { [P in K]: { [Q in R[P]]: P; }; }
    : never;

type UnionToIntersection<U> =
    (U extends any ? (k: U) => void : never) extends ((k: infer I) => void)
    ? I
    : never;

type Inverse<R extends Record<string, string>> =
    Intermediate<R> extends Record<string, infer T>
    ? { [K in keyof UnionToIntersection<T>]: UnionToIntersection<T>[K]; }
    : never;

type InverseMappedType = Inverse<MappedType>;
// type InverseMappedType = {
//     value1: 'key1';
//     value2: 'key2';
// }

Another benefit of this approach is it outputs a mapped type with an appropriate property value never when the input record contains duplicate property values:

type MappedType = {
    key1: 'value1';
    key2: 'value1' | 'value2';
};

type InverseMappedType = Inverse<MappedType>;
// type InverseMappedType = {
//     value1: never;
//     value2: 'key2';
// }

Someone more well-versed than me in TypeScript may know a shorter method than this to inverse a mapped type, but this seems to get the job done at least.

Upvotes: 2

Related Questions