Reputation: 1552
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
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
Reputation: 14395
You can use mapped types with the as
operator.
type Inverse<T> = {[K in keyof T as (T[K] & (string | number))]: K};
Upvotes: 2
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
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