键入lodash _.get的安全版本-有条件地解构数组类型

cae*_*say 5 javascript typescript lodash typescript-typings

长期以来,我们一直存在一个问题,即安全,轻松地访问嵌套属性的唯一方法是使用_.get。例如:

_.get(obj, "Some.Nested[2].Property", defaultValue);
Run Code Online (Sandbox Code Playgroud)

这很好用,但不能承受频繁发生的属性重命名。从理论上讲,应该可以将以上内容转换为以下内容,并允许TypeScript隐式地对其进行类型检查:

safeGet(obj, "Some", "Nested", 2, "Property", defaultValue);
Run Code Online (Sandbox Code Playgroud)

我成功地为除数组类型之外的所有内容创建了这样的类型:

function getSafe<TObject, P1 extends keyof TObject>(obj: TObject, p1: P1): TObject[P1];

function getSafe<TObject, P1 extends keyof TObject, P2 extends keyof TObject[P1]>(obj: TObject, p1: P1, p2: P2): TObject[P1][P2];
Run Code Online (Sandbox Code Playgroud)

这样可以正确地检查深度项目(我将自动生成这些语句到10个级别左右)。数组属性失败,因为传递给下一个参数的类型为T[]and not T

由于将自动生成代码,因此无需考虑任何解决方案的复杂性或冗长性,问题是我似乎找不到类型声明的任何组合,这些组合允许我接受整数参数并解构数组类型移动向前。

您可以使用来解构一个数组(其中T是一个数组)T[number]。问题是我无法限制T嵌套属性上的数组在哪里。

function getSafe<TObject, P1 extends keyof TObject, P2 extends keyof TObject[P1][number]>(obj: TObject, p1: P1, index: number, p2: P2): TObject[P1][number][P2];
                                                                                 ^^^^^^                                                             ^^^^^^
const test2 = getSafe(obj, "Employment", 0, "Id"); // example usage
Run Code Online (Sandbox Code Playgroud)

这实际上可以在调用站点进行(那里没有错误,可以正确地给我们参数和返回类型),但是在声明本身中却给我们带来了错误,因为您不能使用索引TObject[P1][number]因为我们不能保证TObject[P1]是数组。

注意:这TType[number]是从数组类型获取元素类型的可行方法,但是我们需要说服编译器,我们正在对数组执行此操作

问题是,是否确实要向其中添加数组约束,TObject[P1] 或者是否有其他方法无法实现此目的?

cae*_*say 4

我已经弄清楚了这一点并在这里发布了一个 npm 包:ts-get-safe

关键点是弄清楚如何有条件地将数组重组为其元素类型。为此,您首先必须断言所有属性都是数组或never. 求解方程的类型是:

type GSArrEl<TKeys extends keyof TObj, TObj> = { [P in TKeys]: undefined[] & TObj[P] }[TKeys][number];
Run Code Online (Sandbox Code Playgroud)

神奇之处在于我们基本上联合了to{ [P in TKeys]: undefined[] & TObj[P] }的每个属性。因为我们确定每个属性要么是一个数组,要么(它将位于每个不是数组的属性上),所以我们可以执行解构表达式来获取元素类型。TObjundefined[]nevernever[number]

这是同时发生两个数组解构的示例:

function getSafe<TObject, P0 extends keyof TObject, A1 extends GSArrEl<P0, TObject>, P2 extends keyof A1, P3 extends keyof A1[P2], A4 extends GSArrEl<P3, A1[P2]>>(obj: TObject, p0: P0, a1: number, p2: P2, p3: P3, a4: number): A4;
Run Code Online (Sandbox Code Playgroud)

我的库中已经生成了数百种数组和对象属性的组合ts-get-safe,并且可以使用,但是我仍然愿意以通用方式改进它,以便我们可以在同一声明中使用动态数量的参数。甚至是将数组和属性导航组合到同一类型约束中的方法,这样我们就不必生成数组和属性访问的每个变体。