我需要(如果可能)在TypeScrip中映射类型,其中我有一个由{ readonly name: string, readonly type: 'string' | 'number' }个对象组成的元组,我想将其映射到一个类型,其中name作为键,它们对应的type(映射到实际类型)作为它们的类型.

例如,对于输入

const input = [
  { name: "foo", type: "string" },
  { name: "bar", type: "number" }
] as const

我需要一个像这样的类型:

type ExpectedOutput = {
  foo: string,
  bar: number
}

所以我正在搜索一个类型映射X,使X<typeof input>ExpectedOutput匹配.

映射type非常简单明了:

type Type = "string" | "number";

type MapType<T extends Type> = T extends "string"
  ? string
  : number

这样,映射单个元组条目就很容易了(并且工作正常):

type X_1<T extends { readonly name: string; readonly type: Type }> = {
  [K in T["name"]]: MapType<T["type"]>;
};

const example: X_1<{ name: "age"; type: "number" }> = {
  age: 42
}; // correct
const example2: X_1<{ name: "age"; type: "number" }> = {
  age: "42"
}; // type error

只有当我try 拥有一组值时,我才会遇到问题:

type X<T extends readonly { readonly name: string; readonly type: Type }[]> = {
  [K in T[number]["name"]]: MapType<T[number]["type"]>;
};

const tuple = [{ name: "age", type: "number" }, { name: "name", type: "string" }] as const;

const example3: X<typeof tuple> = {
  age: 42,
  name: "John"
}; // correct
const example4: X<typeof tuple> = {
  age: "42",
  name: "John"
}; // should have a type error, but doesn't

无论我try 做什么(我已经try 了几十种变体),这些属性的计算结果总是为any of the tuple's types(在本例中为string | number),这是合理的,因为[number]返回所有可能选项的并集.

我已经很久没有用打字字体做过深度"黑魔法"了,所以我甚至不太确定这是否可能.

提前感谢您的帮助/指点.

PS:这个问题(为简单起见我略go 了)的背景是,我正在构建一个库,该库在给定一组特定参数的情况下,应该构建一个zod模式,该模式在验证后可以解析为正确的类型.

推荐答案

在您的mapped type版本(可能还有几十个变体)中,属性值类型没有提到属性键类型K,因此它不可能依赖于特定的键.也就是说,您的键K遍历union类型T[number]["name"],但值涉及另一种联合类型T[number]["type"],并且与K没有关系,生成

const tuple = [
    { name: "age", type: "number" },
    { name: "name", type: "string" }
] as const;
type Z = X<typeof tuple>;
/* type Z = {
    age: string | number;
    name: string | number;
} */

解决这个问题的一种方法是使用key remapping in mapped types,这样您就不会在T[number]["name"]上迭代K,而是在T[number]上迭代类型参数U,并使用U["name"]作为键,使用U["type"]来生成值:

type X<T extends readonly { readonly name: string; readonly type: Type }[]> = {
    [U in T[number] as U["name"]]: MapType<U["type"]>;
};

现在,当你使用它时,你会得到:

const tuple = [
    { name: "age", type: "number" },
    { name: "name", type: "string" }
] as const;
type Z = X<typeof tuple>
/* type Z = {
    age: number;
    name: string;
}*/

这就是你想要的类型.

Playground link to code

Typescript相关问答推荐

类型脚本如何将嵌套的通用类型展开为父通用类型

您可以创建一个类型来表示打字员吗

类型断言添加到类型而不是替换

angular 17独立使用多个组件(例如两个组件)

将props传递给子组件并有条件地呈现它''

如何键入函数以只接受映射到其他一些特定类型的参数类型?

如何使用一个字段输入对象,其中每个键只能是数组中对象的id属性,而数组是另一个字段?

如何在Angular 12中创建DisplayBlock组件?

APIslice-CreateAPI的RTK打字错误

访问继承接口的属性时在html中出错.角形

TypeError:正文不可用-NextJS服务器操作POST

在排版修饰器中推断方法响应类型

如何缩小函数的返回类型?

如何在Deno中获取Base64图像的宽/高?

如何使用警告实现`RecursiveReadonly<;T>;`,如果`T`是原语,则返回`T`而不是`RecursiveReadonly<;T>;`

为什么看起来相似的代码在打字时会产生不同的错误?

基于闭包类型缩小泛型类型脚本函数的范围

数据库中的表请求返回如下日期:2023-07-20T21:39:00.000Z 我需要将此数据格式化为 dd/MM/yyyy HH:mm

是否有解释为什么默认情况下在泛型类型上访问的属性不是索引访问

req.files = 未定义(Multer、Express、Typescript)