You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过字符串路径让TypeScript推断对象非空值?

如何通过字符串路径让TypeScript推断对象属性非空?

问题背景

假设有一个键类型可为任意类型(包括null和undefined)的动态对象,需要实现一个函数:接收该对象的字符串路径数组,当所有路径对应的对象值均不为null或undefined时返回true。目标是让TypeScript自动推断这些路径对应值非空,从而无需使用!非空断言操作符。

示例代码如下:

interface Foo {
  a?: { i?: number; ii?: number },
  b?: string;
  c?: {}
}
var object: Foo = {
    a: {
        i: undefined,
        ii: 1
    },
    b: undefined,
    c: {}
}

if (exists(object, ["a.ii", "c"])) // 当`object.a.ii`和`object.c`非null/undefined时返回true
{
    // 期望TypeScript推断`object.a.ii`非空,无需使用`!`
    let value: number = object.a.ii!;
}

已知单个值的非空推断可以这样实现:

function exists<T>(object: T | null | undefined): object is T
{
    return object !== null && object !== undefined;
}

if (exists(object.a.ii) && exists(object.c))
{
    let value: number = object.a.ii; // 无需`!`,可正常运行
}

现在需要知道能否通过字符串路径的方式实现上述非空推断效果。

解决方案

可以实现,核心是利用TypeScript的条件类型和模板字面量类型解析路径,结合类型谓词来缩小对象的类型范围。

1. 定义路径解析工具类型

首先需要工具类型将字符串路径拆解为属性层级,并获取对应属性类型:

// 拆分字符串路径为属性数组类型
type SplitPath<S extends string> = S extends `${infer Head}.${infer Tail}`
  ? [Head, ...SplitPath<Tail>]
  : [S];

// 根据属性数组获取对应层级的类型
type GetPathType<T, Path extends string[]> = Path extends [infer First extends keyof T, ...infer Rest extends string[]]
  ? GetPathType<T[First], Rest>
  : T;

2. 定义非空标记工具类型

接下来实现工具类型,将指定路径的所有节点标记为非null/undefined:

// 递归标记单一路径上的属性为非空
type MarkPathAsNonNullable<T, Path extends string[]> = Path extends [infer First extends keyof T, ...infer Rest extends string[]]
  ? T extends null | undefined
    ? never // 若当前节点为空,直接返回never,避免后续无效访问
    : {
        [K in keyof T]: K extends First
          ? MarkPathAsNonNullable<NonNullable<T[K]>, Rest>
          : T[K]
      }
  : NonNullable<T>;

// 批量处理多个路径的类型标记
type MarkPathsAsNonNullable<T, Paths extends string[]> = Paths extends [infer P extends string, ...infer Rest extends string[]]
  ? MarkPathsAsNonNullable<MarkPathAsNonNullable<T, SplitPath<P>>, Rest>
  : T;

3. 实现带类型推断的exists函数

最后编写exists函数,结合运行时检查和类型谓词:

function exists<T, Paths extends string[]>(
  obj: T,
  paths: Paths
): obj is MarkPathsAsNonNullable<T, Paths> {
  // 运行时遍历所有路径,逐一检查属性是否非空
  return paths.every(path => {
    let current: any = obj;
    const keys = path.split('.');
    for (const key of keys) {
      if (current === null || current === undefined) return false;
      current = current[key];
    }
    return current !== null && current !== undefined;
  });
}

测试使用

用开头的示例验证效果:

interface Foo {
  a?: { i?: number; ii?: number },
  b?: string;
  c?: {}
}
var object: Foo = {
    a: {
        i: undefined,
        ii: 1
    },
    b: undefined,
    c: {}
}

if (exists(object, ["a.ii", "c"])) {
    // TypeScript自动推断object.a.ii为number,object.c为{},无需非空断言
    let value: number = object.a.ii;
    let cValue: {} = object.c;
    // 错误示例:如果访问未检查的路径,仍会报错
    // let bValue: string = object.b; // 类型错误:object.b可能为undefined
}

限制说明

  • 仅支持静态字面量路径,动态生成的字符串路径无法被TypeScript编译时解析,因此无法触发类型推断。
  • 如果路径中的中间节点本身是null/undefined,类型会被推断为never,对应的if分支会被TypeScript判定为不可达,符合运行时逻辑。

内容的提问来源于stack exchange,提问作者匿名

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 09:07:47