Core Docs
@skyroc/utils

Path

深层路径工具:解析 name path、读取 / 写入 / 删除嵌套值,并提供路径标记、路径收集与安全 key 保护

概述

import { deepGet, deepSet, deepUnset, keyOfName, toPathArray, toSegments, unflatten } from '@skyroc/utils';

path 模块提供一组面向对象 / 数组深层字段的工具,主要用于表单状态、字段依赖、脏标记和嵌套数据转换。

常规使用直接从 @skyroc/utils 主入口导入即可。主入口同时 re-export radash,因此路径读写函数使用 deepGetdeepSetdeepUnsetunflatten 这组名字,避免和 radashgetsetconstruct 语义冲突。

路径表示

路径工具统一使用 NamePath 表示字段路径:

type Key = string | number;
type PathTuple = readonly Key[];
type NamePath = Key | PathTuple | undefined;

常见写法:

写法解析结果说明
'profile.name'['profile', 'name']点路径
'items[0].title'['items', 0, 'title']数组下标会转成 number
'items["sku.code"]'['items', 'sku.code']带点的 key 可用引号括起来
['profile', 'name']['profile', 'name']tuple 路径会复制一份
0[0]单个 number key

toPathArray(path)

将字符串路径解析为数组片段。

toPathArray('user.addresses[0].city');
// ['user', 'addresses', 0, 'city']

toPathArray('items["sku.code"]');
// ['items', 'sku.code']

toPathArray("items['sku.code']");
// ['items', 'sku.code']

toPathArray('items[01]');
// ['items', '01'],非规范数字下标保留为字符串

toSegments(path)

NamePath 规范化为路径片段数组。

toSegments('user[0]');
// ['user', 0]

toSegments(['user', 0]);
// ['user', 0]

toSegments(0);
// [0]

业务代码里通常传字符串、number 或 tuple。deepGet 会把 null / undefined 当作空路径并返回默认值;deepSet / deepUnset 如果需要明确 no-op,传空数组 []

keyOfTuple(tuple) / keyOfName(name)

将路径片段拼成稳定的点路径字符串,适合做 Set / Map 的 key。

keyOfTuple(['profile', 'name']);
// 'profile.name'

keyOfName('profile.name');
// 'profile.name'

keyOfName(['profile', 'name']);
// 'profile.name'

读取值

deepGet(obj, path, def?)

安全读取嵌套值。路径不存在、路径为空、读取过程中遇到非对象值,都会返回默认值。

import { deepGet } from '@skyroc/utils';

const source = {
  items: [{ title: 'First' }],
  profile: {
    age: undefined,
    name: 'Alex',
    nullable: null,
  },
};

deepGet(source, 'profile.name');
// 'Alex'

deepGet(source, ['items', 0, 'title']);
// 'First'

deepGet(source, 'items[0].title');
// 'First'

deepGet(source, 'profile.missing', 'fallback');
// 'fallback'

deepGet(source, 'profile.age', 'fallback');
// 'fallback'

deepGet(source, 'profile.nullable', 'fallback');
// null,显式 null 会被保留

deepGetundefinednull 的语义不同:

结果
目标值是 undefined返回默认值
目标值是 null返回 null
pathnull / undefined / []返回默认值

写入值

deepSet(obj, path, value, options?)

不可变写入嵌套值。原对象不会被修改;沿路径经过的对象 / 数组会被复制,缺失的中间容器会按路径片段自动创建。

import { deepSet } from '@skyroc/utils';

const source = { profile: { name: 'Alex' } };
const next = deepSet(source, 'profile.age', 18);

next;
// { profile: { name: 'Alex', age: 18 } }

source;
// { profile: { name: 'Alex' } }

数字路径片段会创建数组容器:

deepSet({}, 'items[0].title', 'First');
// { items: [{ title: 'First' }] }

deepSet(undefined, [0, 'title'], 'First');
// [{ title: 'First' }]

空路径会直接返回原值:

const source = { profile: { name: 'Alex' } };

deepSet(source, [], 'Next') === source;
// true

deepUnset(obj, path, options?)

不可变删除嵌套值。删除对象字段时会移除 key;删除数组下标时会使用 splice,后续元素会前移。

import { deepUnset } from '@skyroc/utils';

const source = {
  items: ['first', 'second'],
  profile: { age: 18, name: 'Alex' },
};

deepUnset(source, 'profile.age');
// { items: ['first', 'second'], profile: { name: 'Alex' } }

deepUnset(source, ['items', 0]);
// { items: ['second'], profile: { age: 18, name: 'Alex' } }

当根值不是对象 / 数组,或路径为空时,deepUnset 不会创建新结构:

deepUnset(1, 'profile.name');
// 1

deepUnset(source, []) === source;
// true

安全 key

deepSetdeepUnset 默认开启 safeKeys,会阻止以下危险 key:

  • __proto__
  • constructor
  • prototype
deepSet({}, '__proto__.polluted', true);
// {}

({} as Record<string, unknown>).polluted;
// undefined

如确实需要把这些字符串当作普通业务字段写入,可以显式关闭:

deepSet({}, 'constructor.value', 1, { safeKeys: false });
// { constructor: { value: 1 } }

扁平对象转嵌套对象

unflatten(obj)

将以路径字符串为 key 的扁平对象展开为嵌套结构,适合把表单字段快照、查询结果或补丁对象还原为业务对象。

import { unflatten } from '@skyroc/utils';

unflatten({
  'items[0].title': 'First',
  'profile.name': 'Alex',
});

// {
//   items: [{ title: 'First' }],
//   profile: { name: 'Alex' },
// }

传入空值时返回空对象:

unflatten(null as any);
// {}

路径状态标记

以下函数用于把字段路径规范化为字符串 key 后写入 Set<string>,常见于 dirty、touched、validating 等字段状态集合。

import { allOn, anyOn, flagOff, flagOn, isOn } from '@skyroc/utils';

const flags = new Set<string>();

flagOn(flags, ['profile', 'name']);

isOn(flags, 'profile.name');
// true

anyOn(flags, [['profile', 'name']]);
// true

allOn(flags, [['profile', 'name']]);
// true

flagOff(flags, 'profile.name');

isOn(flags, ['profile', 'name']);
// false

anyOn(set)allOn(set) 在没有传 names 时,都会退化为判断集合是否非空。

路径收集

collectChangedLeafPaths(input)

收集对象里的叶子路径。数组节点本身也会作为一条变更路径返回,这样上层订阅数组字段时可以收到数组结构变化。

collectChangedLeafPaths({
  items: [{ title: 'First' }],
  profile: { name: 'Alex' },
});

// [
//   ['items'],
//   ['items', '0', 'title'],
//   ['profile', 'name'],
// ]

unionPaths(a, b)

合并两组路径,按第一次出现的顺序去重。

unionPaths(
  [
    ['profile', 'name'],
    ['items', 0],
  ],
  [
    ['items', 0],
    ['profile', 'age'],
  ]
);

// [
//   ['profile', 'name'],
//   ['items', 0],
//   ['profile', 'age'],
// ]

isUnderPrefix(key, prefix)

判断路径是否落在某个前缀下面,支持精确匹配、点路径子级匹配,以及 * / 空字符串通配。

isUnderPrefix('profile.name', 'profile');
// true

isUnderPrefix('profile', 'profile');
// true

isUnderPrefix('profileName', 'profile');
// false

isUnderPrefix('profile.name', '*');
// true

collectDeepKeys(obj)

递归收集深层叶子 key。nullundefined、非对象值和 Date 会被视为叶子;空对象会返回当前路径。

collectDeepKeys({
  empty: {},
  list: [1],
  nil: null,
  profile: {
    birthday: new Date('2026-05-07T00:00:00.000Z'),
    name: 'Alex',
  },
});

// [
//   'empty',
//   'list.0',
//   'nil',
//   'profile.birthday',
//   'profile.name',
// ]

类型守卫与容器工具

这些工具主要供表单内核和路径写入逻辑复用。

函数说明
isPlainObject(value)判断是否为普通对象,数组会返回 false
isObjectRecord(value)判断是否为非 null 对象,数组也会返回 true
isObjectLike(value)判断是否为非 null 对象
isUnsafeKey(key)判断 key 是否为 __proto__constructorprototype
emptyContainer(nextKey)nextKey 是 number 时返回 [],否则返回 {}

微任务调度

microtask(cb)

优先使用 queueMicrotask,不可用时降级为 Promise.resolve().then(cb)

import { microtask } from '@skyroc/utils';

microtask(() => {
  // 在当前同步任务结束后执行
});

On this page