问题
In lodash, the _.invert function inverts an object's keys and values:
var object = { 'a': 'x', 'b': 'y', 'c': 'z' };
_.invert(object);
// => { 'x': 'a', 'y': 'b', 'z': 'c' }
The lodash typings currently declare this to always return a string→string mapping:
_.invert(object); // type is _.Dictionary<string>
But sometimes, especially if you're using a const assertion, a more precise type would be appropriate:
const o = {
a: 'x',
b: 'y',
} as const; // type is { readonly a: "x"; readonly b: "y"; }
_.invert(o); // type is _.Dictionary<string>
// but would ideally be { readonly x: "a", readonly y: "b" }
Is it possible to get the typings this precise? This declaration gets close:
declare function invert<
K extends string | number | symbol,
V extends string | number | symbol,
>(obj: Record<K, V>): {[k in V]: K};
invert(o); // type is { x: "a" | "b"; y: "a" | "b"; }
The keys are right, but the values are the union of the input keys, i.e. you lose the specificity of the mapping. Is it possible to get this perfect?
回答1:
You can do it using a more complicated mapped type that preserves the correct value:
const o = {
a: 'x',
b: 'y',
} as const;
type AllValues<T extends Record<PropertyKey, PropertyKey>> = {
[P in keyof T]: { key: P, value: T[P] }
}[keyof T]
type InvertResult<T extends Record<PropertyKey, PropertyKey>> = {
[P in AllValues<T>['value']]: Extract<AllValues<T>, { value: P }>['key']
}
declare function invert<
T extends Record<PropertyKey, PropertyKey>
>(obj: T): InvertResult<T>;
let s = invert(o); // type is { x: "a"; y: "b"; }
AllValues first creates a union that contains all key, value pairs (so for your example this will be { key: "a"; value: "x"; } | { key: "b"; value: "y"; }). In the mapped type we then map over all value types in the union and for each value we extract the original key using Extract. This will work well as long as there are no duplicate values (if there are duplicate values we will get a union of the keys wehere the value appears)
回答2:
The solution by Titian Cernicova-Dragomir is really cool. Today I found another alternative to swap object keys and values with Conditional Types:
type KeyFromValue<V, T extends Record<PropertyKey, PropertyKey>> = {
[K in keyof T]: V extends T[K] ? K : never
}[keyof T];
type Invert<T extends Record<PropertyKey, PropertyKey>> = {
[V in T[keyof T]]: KeyFromValue<V, T>
};
Test with const o:
const o = {
a: "x",
b: "y"
} as const;
// type Invert_o = {x: "a"; y: "b";}
type Invert_o = Invert<typeof o>;
// works
const t: Invert<typeof o> = { x: "a", y: "b" };
// Error: Type '"a1"' is not assignable to type '"a"'.
const t1: Invert<typeof o> = { x: "a1", y: "b" };
Declare the invert function in the same manner like above answer with Return type Invert<T>.
Playground
来源:https://stackoverflow.com/questions/56415826/is-it-possible-to-precisely-type-invert-in-typescript