深层路径工具:解析 name path、读取 / 写入 / 删除嵌套值,并提供路径收集、前缀匹配与安全 key 保护
import { deepGet, deepSet, deepUnset, keyOfName, toPathArray, toSegments, unflatten } from '@skyroc/utils';path 模块提供一组面向对象 / 数组深层字段的工具,主要用于表单状态、字段依赖、脏标记和嵌套数据转换。
常规使用直接从 @skyroc/utils 主入口导入即可,也可以走 @skyroc/utils/path 子入口。
函数名刻意用了 deepGet / deepSet / deepUnset / unflatten 这一组,而不是 get / set / construct——
radash 自带同名函数且语义重叠,用不同的名字避免调用方不知道该用哪个(这也是主入口只白名单转出 radash 少量导出的原因之一)。
路径工具统一使用 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 会被保留deepGet 对 undefined 和 null 的语义不同:
| 值 | 结果 |
|---|---|
目标值是 undefined | 返回默认值 |
目标值是 null | 返回 null |
path 是 null / 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;
// truedeepUnset(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;
// truedeepSet 和 deepUnset 默认开启 safeKeys,会阻止以下危险 key:
__proto__constructorprototypedeepSet({}, '__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);
// {}isUnderPrefix(key, prefix)判断路径是否落在某个前缀下面,支持精确匹配、点路径子级匹配,以及 * / 空字符串通配。
isUnderPrefix('profile.name', 'profile');
// true
isUnderPrefix('profile', 'profile');
// true
isUnderPrefix('profileName', 'profile');
// false
isUnderPrefix('profile.name', '*');
// truecollectDeepKeys(obj)递归收集深层叶子 key。null、undefined、非对象值和 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__、constructor 或 prototype |
toArrayIndex(key) | key 是合法数组下标(非负整数、无前导零)时返回该数字,否则返回 null |
emptyContainer(nextKey) | nextKey 是数组下标时返回 [],否则返回 {} |
| 类型 | 说明 |
|---|---|
Key | string | number |
PathTuple | readonly Key[] |
NamePath | Key | PathTuple | undefined,所有路径参数接受的形态 |
SetOptions | deepSet / deepUnset 的选项(safeKeys) |
Last updated on