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

如何为Lodash的set函数添加TypeScript类型安全性?

为Lodash的set函数添加TypeScript类型安全性

Lodash原生的set函数因为类型设计的局限性,无法对路径做严格的类型校验。我们可以自己实现一个类型安全的版本,通过TypeScript递归类型约束路径,同时保留自动创建嵌套对象的能力,且不会覆盖已有属性。

1. 定义合法路径的递归类型

首先需要一个递归类型,用来提取对象的所有合法嵌套路径,支持可选属性:

type Path<T> = T extends object
  ? {
      [K in keyof T]: K extends string | number
        ? `${K}` | `${K}.${Path<T[K]>}`
        : never;
    }[keyof T]
  : never;

这个类型会递归遍历对象的所有可访问属性,生成以点分隔的合法路径字符串,无效路径会被TypeScript直接拦截。

2. 实现类型安全的set函数

接下来实现核心函数,处理路径解析、嵌套对象自动补全,同时避免覆盖已有对象属性:

function safeSet<T extends object>(obj: T, path: Path<T>, value: unknown): void {
  const keys = path.split('.');
  let current: any = obj;

  // 遍历路径的前置层级,补全缺失的嵌套对象
  for (let i = 0; i < keys.length - 1; i++) {
    const key = keys[i];
    if (!current[key] || typeof current[key] !== 'object') {
      current[key] = {};
    }
    current = current[key];
  }

  const finalKey = keys[keys.length - 1];
  // 仅当目标属性不存在或不是对象时赋值,避免覆盖已有对象
  if (current[finalKey] == null || typeof current[finalKey] !== 'object') {
    current[finalKey] = value;
  }
}

3. 使用示例

现在可以用这个函数,TypeScript会自动校验路径合法性:

interface User {
  id: number;
  profile?: {
    name: string;
    address?: {
      city: string;
    };
  };
}

const user: User = { id: 1 };

// 合法路径,无类型错误
safeSet(user, 'profile.name', 'Alice');
safeSet(user, 'profile.address.city', 'New York');

// 无效路径,TypeScript直接报错
safeSet(user, 'profile.age', 25); // 错误:类型'"profile.age"'不能赋值给类型'Path<User>'
safeSet(user, 'invalidKey', 'test'); // 错误:类型'"invalidKey"'不能赋值给类型'Path<User>'

4. 扩展:支持数组索引路径

如果需要支持数组索引(比如'profile.addresses[0].city'),可以修改类型和解析逻辑:

// 更新路径类型,支持数组索引
type Path<T> = T extends (infer U)[]
  ? `[${number}]` | `[${number}].${Path<U>}`
  : T extends object
  ? {
      [K in keyof T]: K extends string | number
        ? `${K}` | `${K}.${Path<T[K]>}`
        : never;
    }[keyof T]
  : never;

// 更新函数,处理数组索引解析
function safeSet<T extends object | any[]>(obj: T, path: Path<T>, value: unknown): void {
  const segments = path.split(/\.|\[|\]/).filter(Boolean);
  let current: any = obj;

  for (let i = 0; i < segments.length - 1; i++) {
    let key = segments[i];
    // 处理数组索引
    if (!isNaN(Number(key))) {
      key = Number(key);
      if (!Array.isArray(current[key])) {
        current[key] = [];
      }
    } else {
      if (!current[key] || typeof current[key] !== 'object') {
        current[key] = {};
      }
    }
    current = current[key];
  }

  let finalKey = segments[segments.length - 1];
  if (!isNaN(Number(finalKey))) {
    finalKey = Number(finalKey);
  }
  if (current[finalKey] == null || typeof current[finalKey] !== 'object') {
    current[finalKey] = value;
  }
}

数组路径使用示例:

interface User {
  addresses?: { city: string }[];
}

const user: User = {};
safeSet(user, 'addresses[0].city', 'London'); // 合法
safeSet(user, 'addresses[1].zip', '12345'); // 合法
safeSet(user, 'addresses[0].invalid', 'test'); // TypeScript报错

这个实现保留了Lodash set的核心功能,同时通过TypeScript类型系统强制路径合法性,避免无效路径的问题,且默认不会覆盖已有对象属性(如果需要覆盖,可去掉对应判断逻辑)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 05:22:34