Typescript:设置深度嵌套对象路径的值类型

sas*_*ank 1 typescript

我想找到一种方法来拥有value types嵌套对象路径的所有键。

我在一定程度上取得了成功,但未能为数组对象内的深层嵌套属性设置值类型。

interface BoatDetails {
  boats: {
    boat1: string;
    boat2: number;
  };
  ships: Array<Ship>
}
interface Ship {
    shipName: string
}
const boatDetails: BoatDetails = {
  boats: {
    boat1: "lady blue",
    boat2: 1,
  },
  ships: [
      {shipName: "lacrose"}
  ]
};
Run Code Online (Sandbox Code Playgroud)

对于上面的代码,我能够成功设置嵌套对象路径的值类型,例如boats.boat1who value type is stringboats.boat2who value type is numbershipswho value type is Array<Ship>

但无法设置value type嵌套路径ships.0.shipName

我从以下链接中参考了设置深层嵌套对象路径类型: Typescript:嵌套对象的深层 keyof

下面是我在打字稿游乐场中设置深层嵌套对象路径的值类型的尝试:

用于查看深层嵌套对象路径的值类型的 Playground 链接

jca*_*alz 5

请注意:带有模板文字类型操作的递归条件类型正在对编译器造成负担(您可以轻松获得显式递归限制警告,或更糟糕的是,编译时间呈指数级增长),并且具有各种边缘情况。Paths<T>PathValue<T, P>


一种边缘情况是,使用模板文字轻松完成的number- 到 -string 文字类型转换没有简单的逆操作将string文字转换为等效number文字(有关更多信息,请参阅此问题和答案)。

因此,您将获得一个索引类型,就像"0"您想要用作数组类型的键一样,但除非该数组类型恰好是 tuple 否则编译器不会让您这样做:

type Oops = (string[])["0"] // error!
// ------------------> ~~~
// Property '0' does not exist on type 'string[]'

type Okay = (string[])[0] // okay
// type Okay = string
Run Code Online (Sandbox Code Playgroud)

并且因为"0"不被视为数组的键,"ships.0.shipName"所以当您的PathValue类型函数求值时会失败"0" extends keyof Ship[],并且您会感到难过。如果没有正式的方法来转换"0"0或让编译器将"0"其视为keyof Ship[],则没有规范的解决方案。


所以你有点陷入各种解决方法的困境。一种可能是忽略元组的可能性(除了元组类型中那些讨厌的其余元素之外,元组大多已经具有显式的数字字符串索引),而只是采取一种解决方法来T[K]检查是否T具有number索引签名并且K可分配给`${number}`,并且如果所以,返回T[number]

type Idx<T, K> = K extends keyof T ? T[K] :
    number extends keyof T ? K extends `${number}` ? T[number] : never : never;
Run Code Online (Sandbox Code Playgroud)

现在有效:

type TryThis = Idx<string[], "0">
// type TryThis = string

type StillWorks = Idx<string[], 0>
// type StillWorks = string
Run Code Online (Sandbox Code Playgroud)

如果我们在您的类型中使用它PathValue<T, P>,如下所示:

type PathValue<T, P extends Paths<T, 4>> = P extends `${infer Key}.${infer Rest}`
  ? Rest extends Paths<Idx<T, Key>, 4>
  ? PathValue<Idx<T, Key>, Rest>
  : never
  : Idx<T, P>
Run Code Online (Sandbox Code Playgroud)

然后事情开始工作:

setValue(
  boatDetails,
  `ships.0.shipName`,
  "titanic"
); // okay
/* function setValue<BoatDetails, "ships.0.shipName">(
     obj: BoatDetails, path: "ships.0.shipName", value: string
): BoatDetails */
Run Code Online (Sandbox Code Playgroud)

还有其他可能的解决方法,它们可能能够从更多任意对T和中得出更准确的结果K,但我认为目前这已经足够好了。

Playground 代码链接