A focused JavaScript and TypeScript library for working safely and precisely with object structures.
@typhonjs-utils/object combines conventional object utilities with a structural property-path system that understands the complete JavaScript PropertyKey space: strings, numeric array indexes, and symbols. It provides typed access and inspection, hardened mutation, bounded traversal, structural comparison, deep object operations, iterable helpers, and trie-backed property-path collections.
The API is designed around predictable runtime semantics and strong TypeScript inference. Known object shapes are preserved by guards and assertions, literal property paths infer exact result types, deep merges infer the accumulated output shape, and collection overloads refine iterator results from literal options.
- Strongly inferred dotted and exact tuple property paths.
- Exact string, number, and symbol path segments without delimiter ambiguity.
- Object guards and assertions that preserve existing interface, class, and union types.
- Getter-safe property existence, descriptor, and owner inspection.
- Prototype-pollution-resistant assignment and deletion.
- Iterative, bounded object traversal with depth, result, and visit controls.
- Trie-backed structural path maps with subtree and candidate-object matching.
- Weak-root structural path associations without retaining root objects.
- Deep merge, freeze, seal, and cloning operations.
- Synchronous and asynchronous iterable validation and non-empty replay.
npm install @typhonjs-utils/objectDirect package import:
import {
PropertyPathMap,
safeAccess,
safeSet
} from '@typhonjs-utils/object';Type inference is a primary design constraint rather than an afterthought.
Dotted paths infer nested object properties:
const state = {
settings: {
enabled: true,
label: 'Primary'
}
};
const enabled = safeAccess(state, 'settings.enabled');
// booleanReadonly tuple paths preserve exact numeric and symbol segments through a const type parameter:
const metadata = Symbol('metadata');
const state = {
entries: [
{ id: 1 },
{ id: 2 }
],
[metadata]: {
active: true
}
};
const id = safeAccess(state, ['entries', 1, 'id']);
// number
const active = safeAccess(state, [metadata, 'active']);
// booleanA supplied default participates in the result type only when the property path may resolve to undefined:
declare const options: {
label?: string;
};
const label = safeAccess(options, 'label', 'Untitled');
// stringKnown object types remain known:
interface Options
{
enabled?: boolean;
timeout?: number;
}
declare const value: Options | undefined;
if (isObject(value))
{
value.timeout;
// value: Options
}Assertions remove invalid union members without widening the surviving type:
assertPlainObject(value);
// value: OptionsUse isRecord when generic string-key indexing is desired, or assertRecord when the existing shape should be preserved while adding PropertyKey indexing.
deepMerge accumulates source shapes in order and reflects later-key precedence:
const result = deepMerge(
{ enabled: false },
{ theme: 'dark' as const },
{ enabled: true, retries: 3 }
);
// {
// theme: 'dark';
// enabled: boolean;
// retries: number;
// }Path-aware APIs accept:
type PropertyPath = string | readonly PropertyKey[];Dotted strings are concise for ordinary nested string keys:
safeAccess(data, 'user.profile.name');Exact arrays preserve keys that dotted syntax cannot represent:
safeAccess(data, ['user.name']); // Literal period.
safeAccess(data, ['entries', 0]); // Numeric array index.
safeAccess(data, [metadata, 'active']); // Symbol key.
safeAccess(data, ['', 'value']); // Empty-string key.Numeric array indexes must be numbers in the ECMAScript array-index range. String indexes such as '0' are intentionally rejected when traversing arrays.
Property paths can also be validated, normalized, concatenated, compared by structural prefix, and converted back to dotted form when the conversion is lossless.
The access API separates value lookup from structural inspection:
const value = getProperty(data, path);
// Preserves present undefined and null.
const valueOrDefault = safeAccess(data, path, fallback);
// Uses fallback for missing or nullish values.
const exists = hasProperty(data, path);
// Does not read the terminal property value.
const descriptor = getPropertyDescriptor(data, path);
// Does not invoke a terminal getter.
const owner = getPropertyOwner(data, path);
// Returns the object or prototype defining the terminal property.safeSet and deleteProperty reject prototype-pollution segments and ECMAScript well-known symbols. safeSet supports direct assignment, conditional assignment, arithmetic updates, and optional creation of missing object segments. deleteProperty supports explicit inherited-property handling and only removes configurable properties.
Property access may invoke getters or proxy traps when intermediate values must be read. Exceptions from user-defined accessors and proxies are intentionally propagated.
pathKeyIterator performs iterative, symbol-aware traversal and yields exact readonly property-key arrays:
for (const path of pathKeyIterator(data, {
arrayIndex: true,
maxDepth: 8,
maxResults: 1_000,
maxVisits: 10_000
}))
{
// path: readonly PropertyKey[]
}Traversal can be constrained to a prefixPath, pruned at a stopPath, restricted to own properties, and bounded by depth, result, and visit budgets.
safeEqual is a source-driven structural comparison. Every enumerable source path must exist in the target and resolve to the same value; additional target properties are ignored. It is intentionally not a symmetric general-purpose deep-equality algorithm.
PropertyPathMap is a map keyed by structural property paths rather than accessor-array identity:
const fields = new PropertyPathMap<string>();
fields.set('actor.system.hp', 'hp');
fields.set(['actor', 'items', 0, 'name'], 'first-item');
fields.set([metadata, 'enabled'], 'metadata-enabled');
fields.get(['actor', 'system', 'hp']);
// 'hp'Equivalent dotted and string-array paths resolve to the same entry. Numeric and string segments remain distinct, and symbols compare by identity.
The collection provides:
Map-styleset,get,has,delete,clear, iteration, andforEach.- Canonical frozen path arrays and insertion-order base iteration.
matchingEntries,matchingKeys, andmatchingValuesfor testing stored paths against a candidate object with shared-prefix pruning.subtreeEntries,subtreeKeys, andsubtreeValuesfor candidate-independent trie traversal.- Absolute
pathPrefixandstopAtbounds. - Optional inclusion of the candidate property value in matching results.
- Configurable path-depth, entry, trie-node, result, and visit limits.
- Atomic insertion preflight so failed limit checks do not leave partial trie state.
- Incremental
nodeCountinspection.
Exact lookup is proportional to path length. Trie-aware matching avoids independently resolving every stored path and rejects unavailable branches at their shared prefix.
WeakPropertyPathMap associates a PropertyPathMap with each weakly held root object or function:
const bindings = new WeakPropertyPathMap<object, string>();
const document = {};
bindings.set(document, 'system.hp.value', 'hp-binding');
bindings.get(document, ['system', 'hp', 'value']);
// 'hp-binding'Each root receives an independent trie and independent resource limits. Roots are not globally enumerable, matching normal WeakMap semantics. Deleting the final path beneath a root removes its per-root trie immediately, while an otherwise unreachable root and all associated paths remain eligible for garbage collection.
The weak collection exposes exact path operations, root membership and deletion, candidate matching, subtree iteration, and constant-time clear.
| Function | Purpose |
|---|---|
assertNonNullObject |
Asserts a non-null object, including arrays, while preserving the value's existing static type. |
assertObject |
Asserts a non-null object, excluding arrays, while preserving the value's existing static type. |
assertObjectOrFunction |
Asserts an object-or-function reference while filtering primitive union members. |
assertOrdinaryObject |
Asserts an ordinary object while preserving the existing static type. |
assertPlainObject |
Asserts a plain object while preserving the existing static type. |
assertRecord |
Preserves the existing type and adds Record<PropertyKey, unknown> indexing. |
isNonNullObject |
Tests for a non-null object, including arrays, while preserving known object types where possible. |
isObject |
Tests for a non-null, non-array object while preserving known object types. |
isObjectOrFunction |
Tests for any non-null object or function, including arrays and constructors. |
isOrdinaryObject |
Tests for the library's tag-based ordinary-object category, including class instances but excluding specialized built-ins. |
isPlainObject |
Tests for an object whose prototype is Object.prototype or null. |
isRecord |
Narrows a non-null, non-array object to Record<string, unknown>. |
NonNullObject<T> |
Extracts the non-null, non-callable object members of a type, including arrays and specialized built-in objects. |
| Export | Purpose |
|---|---|
PropertyPathMap<V> |
Trie-backed structural property-path map with matching, subtree traversal, and defensive limits. |
WeakPropertyPathMap<R, V> |
Weak-root association of independently limited structural property-path maps. |
| Function | Purpose |
|---|---|
concatPropertyPath |
Normalizes and concatenates one or more paths. |
isArrayIndex |
Tests for a numeric ECMAScript array index. |
isPropertyKey |
Tests for a string, number, or symbol property key. |
isPropertyPath |
Validates a non-empty dotted string or exact property-key array. |
isPropertyPathPrefix |
Tests whether one normalized path equals or structurally prefixes another. |
joinPropertyPath |
Converts an exact path to dotted form when lossless. |
normalizePropertyPath |
Converts a path to its canonical readonly property-key array form. |
PropertyPath |
Dotted string or exact readonly PropertyKey array. |
| Function | Purpose |
|---|---|
getProperty |
Resolves a strongly inferred path while preserving present undefined and null. |
getPropertyDescriptor |
Returns the terminal property descriptor without invoking a terminal getter. |
getPropertyOwner |
Returns the object or prototype that owns the terminal property. |
hasProperty |
Tests complete path existence without reading the terminal value. |
safeAccess |
Resolves a strongly inferred path and applies an optional nullish fallback. |
| Function | Purpose |
|---|---|
deleteProperty |
Deletes a configurable path with hardened keys and explicit prototype handling. |
safeSet |
Performs hardened path assignment, conditional assignment, or arithmetic update. |
| Function | Purpose |
|---|---|
pathKeyIterator |
Yields bounded, exact enumerable property paths with symbol and optional array-index support. |
PathKeyIteratorOptions |
Object traversal options including ownership and absolute path bounds. |
PropertyPathTraversalLimits |
Shared maxDepth, maxResults, and maxVisits controls. |
safeEqual |
Performs source-driven structural path and value comparison. |
| Function | Purpose |
|---|---|
deepFreeze |
Iteratively freezes a traversed object graph. |
deepMerge |
Merges source objects into a target with inferred accumulated output typing. |
deepSeal |
Iteratively seals a traversed object graph. |
klona |
Deep-clones a value through the re-exported klona/full implementation. |
| Function | Purpose |
|---|---|
hasAccessor |
Tests for both getter and setter descriptors through the prototype chain. |
hasGetter |
Tests for a getter descriptor through the prototype chain. |
hasPrototype |
Tests whether a constructor matches or inherits from another constructor. |
hasSetter |
Tests for a setter descriptor through the prototype chain. |
| Function | Purpose |
|---|---|
ensureNonEmptyAsyncIterable |
Provides the equivalent behavior for async or synchronous iterable input. |
ensureNonEmptyIterable |
Peeks and returns a replaying iterable only when at least one value exists. |
isAsyncIterable |
Tests for asynchronous iteration. |
isIterable |
Tests for synchronous iteration while intentionally excluding primitive strings. |
| Function | Purpose |
|---|---|
objectKeys |
Returns typed enumerable own string keys with safe fallback behavior. |
objectSize |
Determines the supported size of objects, arrays, maps, sets, and strings. |
- Dotted strings cannot represent literal periods, symbols, numeric array indexes, or a single empty-string key; use exact arrays for those cases.
MapandSetentries are not traversed bypathKeyIteratoror compared structurally bysafeEqual.- Array elements are terminal traversal paths even when they contain objects.
safeEqualis asymmetric and source-driven.safeSetcreates missing intermediate segments as objects, not arrays.- Property access and matching may invoke getters and proxy traps when intermediate or requested terminal values must be read.
- Traversal and collection limits are configurable so trusted workloads can raise them explicitly.
For exhaustive signatures, option semantics, complexity notes, and edge cases, please see the API documentation.
