path
The path utility safely retrieves a nested value from an object using a dot-notation string or an array of keys. It prevents runtime errors when accessing properties of undefined or null objects and supports a customizable fallback value.
Implementation
View Source Code
ts
import { assert } from '../function/assert';
import { isArray } from '../typed/isArray';
import { isNil } from '../typed/isNil';
import { IS_OBJECT_ERROR_MSG, isObject } from '../typed/isObject';
import type { Obj } from '../types';
type PathValue<T, P extends string> = P extends `${infer Key}.${infer Rest}`
? Key extends keyof T
? PathValue<T[Key], Rest>
: undefined
: P extends keyof T
? T[P]
: undefined;
// #region PathOptions
type PathOptions = {
throwOnMissing?: boolean;
allowArrayIndex?: boolean;
};
// #endregion PathOptions
/**
* Retrieves the value at a given path of the object. If the value is undefined, the default value is returned.
*
* @example
* ```ts
* const obj = { a: { b: { c: 3 } }, d: [1, 2, 3] };
*
* getValue(obj, 'a.b.c'); // 3
* getValue(obj, 'a.b.d', 'default'); // 'default'
* getValue(obj, 'd[1]', undefined, { allowArrayIndex: true }); // 2
* getValue(obj, 'e.f.g', 'default', { throwOnMissing: true }); // throws Error
* ```
*
* @template T - The type of the object to query.
* @template P - The type of the path string.
* @param item - The object to query.
* @param path - The path of the property to get.
* @param [defaultValue] - The value returned for undefined resolved values.
* @param [options] - Additional options for value retrieval.
*
* @returns The resolved value.
*
* @throws If throwOnMissing is true and the path doesn't exist.
*/
export function path<T extends Obj, P extends string>(
item: T,
path: P,
defaultValue?: unknown,
options: PathOptions = {},
): PathValue<T, P> | undefined {
assert(isObject(item), IS_OBJECT_ERROR_MSG, { args: { item }, type: TypeError });
const { throwOnMissing = false, allowArrayIndex = false } = options;
const fragments = path.split(/[.[\]]+/).filter(Boolean);
// biome-ignore lint/suspicious/noExplicitAny: -
let current: any = item;
for (const fragment of fragments) {
if (isNil(current) || typeof current !== 'object') {
return handleError(`Cannot read property '${fragment}' of ${current}`, throwOnMissing, defaultValue);
}
current =
allowArrayIndex && isArray(current) && /^\d+$/.test(fragment) ? current[Number(fragment)] : current[fragment];
if (current === undefined) {
return handleError(`Property '${fragment}' does not exist`, throwOnMissing, defaultValue);
}
}
return current as PathValue<T, P>;
}
function handleError<T extends Obj, P extends string>(
message: string,
throwOnMissing: boolean,
defaultValue?: unknown,
): PathValue<T, P> | undefined {
if (throwOnMissing) throw new Error(message);
return defaultValue as PathValue<T, P>;
}Features
- Isomorphic: Works in both Browser and Node.js.
- Safe Access: Never throws if a parent property is missing.
- Flexible Path Formats: Use dot-notation (
'a.b.c') or an array of keys (['a', 'b', 'c']). - Default Value Support: Provide a fallback value for missing paths.
- Type-safe: Supports generic return types for better autocompletion.
API
Type Definitions
ts
type PathOptions = {
throwOnMissing?: boolean;
allowArrayIndex?: boolean;
};ts
function path<T = any>(obj: any, path: string, fallback?: T, options?: PathOptions): T | undefined;Parameters
obj: The object to query.path: The path to the desired property as a dot-separated string.fallback: Optional. A value to return if the path does not exist or resolves toundefined.options: Optional configuration:throwOnMissing: Iftrue, throws an error instead of returning the fallback value (defaults tofalse).allowArrayIndex: Iftrue, supports bracket notation for array indices (e.g.,'d[1]') (defaults tofalse).
Returns
- The value at the specified path, or the
fallbackvalue, orundefined.
Examples
Basic Nested Access
ts
import { path } from '@vielzeug/toolkit';
const config = {
api: {
endpoints: {
login: '/api/v1/login',
},
},
};
path(config, 'api.endpoints.login'); // '/api/v1/login'
path(config, 'api.version'); // undefined
path(config, 'api.version', 'v1'); // 'v1'Using Array Index with allowArrayIndex
ts
import { path } from '@vielzeug/toolkit';
const data = {
users: [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
],
};
// Access array index with bracket notation
path(data, 'd[1]', undefined, { allowArrayIndex: true }); // 2
path(data, 'users[0].name', undefined, { allowArrayIndex: true }); // 'Alice'Throwing on Missing Paths
ts
import { path } from '@vielzeug/toolkit';
const obj = { a: { b: 1 } };
// Throws an error instead of returning fallback
path(obj, 'e.f.g', 'default', { throwOnMissing: true }); // throws ErrorImplementation Notes
- Performance-optimized for deep traversal.
- Correctly handles numeric keys for array indexing within paths.
- Returns the fallback value if the resolved value is strictly
undefined.