如何获得 TypeScript 映射类型的逆?

Posted

技术标签:

【中文标题】如何获得 TypeScript 映射类型的逆?【英文标题】:How can I get the inverse of a TypeScript mapped type? 【发布时间】:2020-03-22 07:55:01 【问题描述】:

我希望获得 TypeScript 映射类型的“逆”(其属性是严格的字符串,以便“可逆”)。为了说明我想要的结果,我需要一个通用的type

type Inverse<M> = ...

能够变身

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

进入

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

我已经尝试了几件事..但无济于事:

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'>

这甚至可能吗?任何帮助将不胜感激!

【问题讨论】:

你想如何处理type MappedType = key1: 'value1'; key2: 'value1'; ;的反转? 我正在开发一个不仅接收查询而且还发送查询的应用程序。但是,传入查询的格式与传出查询的格式不同,因为它们使用不同的第三方服务。我希望能够以类型安全的方式处理这种双向转换 【参考方案1】:

这是一个精益alternative(除了帕特里克罗伯特的好解决方案):

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"; 

【讨论】:

需要注意的是,当输入映射类型具有重复值时,这会输出一个键的联合,例如Inverse&lt; key1: 'value1'; key2: 'value1' | 'value2'; &gt; == value1: 'key1' | 'key2'; value2: 'key2'; 而不是 value1: never; value2: key2; 。在某些情况下,这种行为甚至可能更可取,所以答案很好。 谢谢你的好提示。是的,我也会认为行为用例依赖。如果需要严格检查,我们可以从here 借用IsUnion 并执行以下操作:type InverseStrict&lt;M extends Record&lt;string, string&gt;&gt; = [K in M[keyof M]]: IsUnion&lt;KeyFromVal&lt;M, K&gt;&gt; extends true ? never: KeyFromVal&lt;M, K&gt; ;。如果我们得到一个键的联合(源对象中的重复值),那将为Inverse 的属性值发出never 类型。【参考方案2】:

您可以通过以下方式完成此操作。它借用了this answer 的一些“邪恶魔法”,在过程的中间步骤将联合转换为交集:

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';
// 

这种方法的另一个好处是,当输入记录包含重复的属性值时,它会输出具有适当属性值never 的映射类型:

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

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

比我更精通 TypeScript 的人可能知道比这更短的方法来反转映射类型,但这似乎至少可以完成工作。

【讨论】:

以上是关于如何获得 TypeScript 映射类型的逆?的主要内容,如果未能解决你的问题,请参考以下文章

如何通过 TypeScript 中的映射类型删除属性

如何从 Typescript 中的固定对象的键创建映射类型

如何在 React 和 Typescript 中映射我的 Union 类型的 GraphQL 响应数组

如何获得 Vue.js 2.0 类型的 TypeScript 与 Visual Studio 一起使用?

typescript Typescript预定义的映射类型

TypeScript 映射类型:带有嵌套的标志类型