是否可以在 TypeScript 中精确键入 _.invert?
Is it possible to precisely type _.invert in TypeScript?
在 lodash 中,_.invert
函数反转对象的键和值:
var object = { 'a': 'x', 'b': 'y', 'c': 'z' };
_.invert(object);
// => { 'x': 'a', 'y': 'b', 'z': 'c' }
lodash 打字 currently 声明它总是 return 一个 string
→string
映射:
_.invert(object); // type is _.Dictionary<string>
但有时,尤其是当您使用 const assertion 时,更精确的类型会更合适:
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" }
打字能做到这么精确吗?此声明接近:
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"; }
键是正确的,但值是输入键的并集,即你失去了映射的特异性。有可能做到这一点吗?
编辑
由于在映射类型中引入了 as
子句,您可以将此类型写为:
You can use a mapped type with an as clause:
type InvertResult<T extends Record<PropertyKey, PropertyKey>> = {
[P in keyof T as T[P]]: P
}
原回答
您可以使用保留正确值的更复杂的映射类型来做到这一点:
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
首先创建一个包含所有 key
、value
对的联合(因此对于您的示例,这将是 { key: "a"; value: "x"; } | { key: "b"; value: "y"; }
)。在映射类型中,我们然后映射联合中的所有 value
类型,对于每个 value
,我们使用 Extract
提取原始 key
。只要没有重复值,这就会很好地工作(如果有重复值,我们将在值出现的地方得到键的并集)
Titian Cernicova-Dragomir 的解决方案真的很酷。今天我找到了另一种使用条件类型交换对象键和值的方法:
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>
};
测试 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" };
以与上述答案相同的方式声明 invert
函数 Return 类型 Invert<T>
。
随着 TypeScript 4.1 对 Key Remapping in Mapped Types 的支持,这变得相当简单:
const o = {
a: 'x',
b: 'y',
} as const;
declare function invert<
T extends Record<PropertyKey, PropertyKey>
>(obj: T): {
[K in keyof T as T[K]]: K
};
let s = invert(o); // type is { readonly x: "a"; readonly y: "b"; }
在 lodash 中,_.invert
函数反转对象的键和值:
var object = { 'a': 'x', 'b': 'y', 'c': 'z' };
_.invert(object);
// => { 'x': 'a', 'y': 'b', 'z': 'c' }
lodash 打字 currently 声明它总是 return 一个 string
→string
映射:
_.invert(object); // type is _.Dictionary<string>
但有时,尤其是当您使用 const assertion 时,更精确的类型会更合适:
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" }
打字能做到这么精确吗?此声明接近:
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"; }
键是正确的,但值是输入键的并集,即你失去了映射的特异性。有可能做到这一点吗?
编辑
由于在映射类型中引入了 as
子句,您可以将此类型写为:
You can use a mapped type with an as clause:
type InvertResult<T extends Record<PropertyKey, PropertyKey>> = {
[P in keyof T as T[P]]: P
}
原回答
您可以使用保留正确值的更复杂的映射类型来做到这一点:
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
首先创建一个包含所有 key
、value
对的联合(因此对于您的示例,这将是 { key: "a"; value: "x"; } | { key: "b"; value: "y"; }
)。在映射类型中,我们然后映射联合中的所有 value
类型,对于每个 value
,我们使用 Extract
提取原始 key
。只要没有重复值,这就会很好地工作(如果有重复值,我们将在值出现的地方得到键的并集)
Titian Cernicova-Dragomir 的解决方案真的很酷。今天我找到了另一种使用条件类型交换对象键和值的方法:
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>
};
测试 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" };
以与上述答案相同的方式声明 invert
函数 Return 类型 Invert<T>
。
随着 TypeScript 4.1 对 Key Remapping in Mapped Types 的支持,这变得相当简单:
const o = {
a: 'x',
b: 'y',
} as const;
declare function invert<
T extends Record<PropertyKey, PropertyKey>
>(obj: T): {
[K in keyof T as T[K]]: K
};
let s = invert(o); // type is { readonly x: "a"; readonly y: "b"; }