ReflectDeep
A powerful TypeScript library for deep reflection operations on JavaScript objects.
Utilities for deep cloning, nested property access, and manipulation with support for circular references and various JavaScript types.
What's New in 2.0
- Zero External Dependencies: Complete rewrite with no external dependencies - lighter and more reliable
- Cleaner API: Internal utility functions are no longer exported, providing a cleaner public API surface
- Dual Mode: Choose between Strict (with runtime type validation) and Non-Strict (without runtime checks) versions
Recommended: set
"type": "module"in your package.json to use this module with ES6 imports.
For more awesome packages, check out my homepage
Features
- Deep Property Access: Provides functions with classic names like
get,set,has,deleteProperty, anddefineProperty. With original functionreach, you can check nested object properties safely - Deep Cloning: Clone complex objects with circular reference handling
- Prototype Chain Inspection: Extract all keys from prototype chain with
ownKeys()or grouped by layer withgroupedKeys() - Type Safety: Full TypeScript support with proper type inference
- Comprehensive Type Support: Handles Arrays, Maps, Sets, Dates, RegExp, TypedArrays, and more
- Circular Reference Safe: Prevents infinite recursion in circular structures
Installation
npm install reflect-deep
Quick Start
Using Non-Strict Mode (Default)
import { ReflectDeep } from 'reflect-deep';
const obj = { a: { e: null, b: [1, 2, { c: 3 }] } };
// Nested property access (no runtime type checks)
ReflectDeep.get(obj, ['a', 'b', 2, 'c']); // 3
ReflectDeep.set(obj, ['a', 'b', 2, 'd'], 'new value');
ReflectDeep.has(obj, ['a', 'b', 2, 'd']); // true
Using Strict Mode (With Runtime Type Validation)
import { ReflectDeepStrict } from 'reflect-deep';
const obj = { a: { e: null, b: [1, 2, { c: 3 }] } };
// Same API, but with runtime type checks for added safety
ReflectDeepStrict.get(obj, ['a', 'b', 2, 'c']); // 3
ReflectDeepStrict.set(obj, ['a', 'b', 2, 'd'], 'new value');
ReflectDeepStrict.has(obj, ['a', 'b', 2, 'd']); // true
// Will throw TypeError for invalid inputs
try {
ReflectDeepStrict.get(null, ['key']); // Throws: target must be an object
} catch (error) {
console.error(error.message);
}
API Reference
Strict vs Non-Strict Mode
2.0 introduces two modes:
ReflectDeep(Non-Strict): Faster, no runtime type validation. Use when you're confident in your inputs.ReflectDeepStrict(Strict): Includes runtime type checks that throwTypeErrorfor invalid inputs. Use for extra safety.
The API is identical between both modes - choose based on your performance vs safety needs.
get(target, propertyKeys[, receiver])
Gets the value of a nested property safely.
Tis the type of returned value. If given, the returned type will be inferred asT | undefinedtarget- Target objectpropertyKeys- Array of property keys forming the pathreceiver- Optional receiver for getter calls (only applies to the final property access)
const obj = { a: { b: { c: 'hello' } } };
const value = ReflectDeep.get(obj, ['a', 'b', 'c']); // 'hello'
const missing = ReflectDeep.get(obj, ['a', 'x', 'y']); // undefined
set(target, propertyKeys, value[, receiver])
Sets a nested property value, creating intermediate objects as needed.
T- ProvideTto validate the type ofvaluetarget- Target objectpropertyKeys- Array of property keys forming the pathvalue- Value to setreceiver- Optional receiver for setter calls (only applies to the final property assignment)
const obj = {};
ReflectDeep.set(obj, ['a', 'b', 'c'], 'hello');
// obj is now { a: { b: { c: 'hello' } } }
has(target, propertyKeys)
Checks if a nested property exists at the given path.
target- Target object to checkpropertyKeys- Array of property keys forming the path
const obj = { a: { b: { c: 'hello' } } };
ReflectDeep.has(obj, ['a', 'b', 'c']); // true
ReflectDeep.has(obj, ['a', 'b', 'd']); // false
reach(target, propertyKeys[, receiver])
Traverses a property path and returns the furthest reachable value with its index.
target- Target object to traversepropertyKeys- Array of property keys forming the pathreceiver- Optional receiver for getter calls (only applies to the final property access)
Returns an object with value (furthest reachable value), index (position reached), and reached (whether the full path was traversed).
const obj = { a: { b: { c: 'hello' } } };
ReflectDeep.reach(obj, ['a', 'b', 'c']); // { value: 'hello', index: 2, reached: true }
ReflectDeep.reach(obj, ['a', 'b', 'd']); // { value: { c: 'hello' }, index: 1, reached: false }
clone(obj)
Creates a deep clone of an object with circular reference handling. Circular reference is fully supported!
obj- Object to clone
const origin = { a: 1, b: { c: 2, o: null } };
origin.b.o = origin; // Circular reference
ReflectDeep.clone(origin); // Deep copy of origin
deleteProperty(target, propertyKeys)
Deletes a nested property at the given path. Has same behavior as the original Reflect.deleteProperty
target- Target objectpropertyKeys- Array of property keys forming the path
Returns true if successful, false otherwise.
const obj = { a: { b: { c: 'hello', d: 'world' } } };
ReflectDeep.deleteProperty(obj, ['a', 'b', 'c']); // true
// obj.a.b is now { d: 'world' }
// Returns true even if property doesn't exist (like original Reflect.deleteProperty)
ReflectDeep.deleteProperty(obj, ['a', 'b', 'nonexistent']); // true
defineProperty(target, propertyKeys, descriptor)
Defines a nested property with the given descriptor, creating intermediate objects as needed. Has same behavior as the original Reflect.defineProperty
target- Target objectpropertyKeys- Array of property keys forming the pathdescriptor- Property descriptor to apply
Returns true if successful, false otherwise.
const obj = {};
// Define a regular property
ReflectDeep.defineProperty(obj, ['a', 'b', 'c'], {
value: 'hello',
writable: true,
enumerable: true,
configurable: true,
});
// obj.a.b.c is now 'hello'
// Define a getter/setter property
ReflectDeep.defineProperty(obj, ['x', 'y'], {
get() {
return this._value;
},
set(v) {
this._value = v;
},
enumerable: true,
configurable: true,
});
ownKeys(target)
Gets all property keys (including symbols) from the target object and its prototype chain as a flattened array.
target- Target object to extract keys from
const obj = { own: 'property', [Symbol('sym')]: 'symbol' };
const allKeys = ReflectDeep.ownKeys(obj);
// Returns: ['own', Symbol(sym), 'toString', 'valueOf', ...]
// Works with custom prototypes
function Parent() {}
Parent.prototype.parentProp = 'parent';
const child = Object.create(Parent.prototype);
child.childProp = 'child';
const keys = ReflectDeep.ownKeys(child);
// ['childProp', 'parentProp', 'toString', ...]
groupedKeys(target)
Gets property keys grouped by prototype layer, preserving the prototype chain structure.
target- Target object to extract grouped keys from
const obj = { own: 'property', [Symbol('sym')]: 'symbol' };
const grouped = ReflectDeep.groupedKeys(obj);
// Returns: [
// { keys: ['own', Symbol(sym)], object: obj },
// { keys: ['toString', 'valueOf', ...], object: Object.prototype },
// ...
// ]
// Useful for inspecting prototype chain structure
function Parent() {}
Parent.prototype.parentProp = 'parent';
const child = Object.create(Parent.prototype);
child.childProp = 'child';
const layers = ReflectDeep.groupedKeys(child);
// layers[0] = { keys: ['childProp'], object: child }
// layers[1] = { keys: ['parentProp'], object: Parent.prototype }
// layers[2] = { keys: ['toString', ...], object: Object.prototype }
Supported:
- Primitive types, Objects, Arrays
- Properties on the prototype chain
- Dates, RegExp, Maps, Sets
- TypedArrays, ArrayBuffer, DataView
- Node.js Buffer, Boxed primitives, BigInt objects
Special Handling:
- Circular References: Automatically detected and handled
- WeakMap/WeakSet/Promise/SharedArrayBuffer: Returns original reference
- Functions: Returns original function reference (no cloning)
Advanced Examples
Prototype Chain Inspection
// Create objects with custom prototype chain
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function () {
return 'noise';
};
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function () {
return 'woof';
};
const myDog = new Dog('Rex', 'German Shepherd');
// Get all keys from entire prototype chain
const allKeys = ReflectDeep.ownKeys(myDog);
// ['name', 'breed', 'bark', 'speak', 'constructor', 'toString', ...]
// Get keys grouped by prototype layer
const layers = ReflectDeep.groupedKeys(myDog);
// [
// { keys: ['name', 'breed'], object: myDog },
// { keys: ['bark', 'constructor'], object: Dog.prototype },
// { keys: ['speak'], object: Animal.prototype },
// { keys: ['toString', 'valueOf', ...], object: Object.prototype }
// ]
Complex Nested Operations
const complex = {
users: [
{ id: 1, profile: { settings: { theme: 'dark' } } },
{ id: 2, profile: { settings: { theme: 'light' } } },
],
};
// Get nested value
const theme = ReflectDeep.get(complex, ['users', 0, 'profile', 'settings', 'theme']);
// Set nested value
ReflectDeep.set(complex, ['users', 0, 'profile', 'settings', 'notifications'], true);
// Check if nested property exists
const hasNotifications = ReflectDeep.has(complex, ['users', 0, 'profile', 'settings', 'notifications']);
Migration from 1.x
Breaking Changes
- Internal utilities removed: Internal helper functions like
$get,$set, etc. are no longer exported. Use the publicReflectDeepAPI instead. - Strict mode opt-in: Runtime type checking is now opt-in via
ReflectDeepStrict. The defaultReflectDeepno longer includes runtime checks for better performance.
Migration Guide
// Before (1.x)
import { $get, $set } from 'reflect-deep';
// After (2.0)
import { ReflectDeep } from 'reflect-deep';
// Use public API methods instead
// Before (1.x) - always had runtime checks
import { ReflectDeep } from 'reflect-deep';
ReflectDeep.get(obj, ['key']); // with runtime checks
// After (2.0) - choose your mode
import { ReflectDeep } from 'reflect-deep';
ReflectDeep.get(obj, ['key']); // no runtime checks (faster)
import { ReflectDeepStrict } from 'reflect-deep';
ReflectDeepStrict.get(obj, ['key']); // with runtime checks (safer)
Performance Considerations
- Zero Dependencies: No external dependencies means faster load times and smaller bundle size
- Mode Selection: Use
ReflectDeepfor performance-critical paths,ReflectDeepStrictfor safety-critical areas - No Depth Limiting: Be careful with very deep object structures to avoid stack overflow
- Circular Reference Cache: Uses WeakMap for efficient circular reference detection
- Type-Specific Optimization: Different cloning strategies for optimal performance per type
Error Handling
Non-Strict Mode (ReflectDeep)
Returns undefined for invalid paths without throwing:
ReflectDeep.get({ a: 1 }, ['x', 'y', 'z']); // undefined (no error)
Strict Mode (ReflectDeepStrict)
Throws TypeError for invalid inputs:
// These will throw TypeError:
ReflectDeepStrict.get(null, ['key']); // non-object target
ReflectDeepStrict.set({}, []); // empty keys array
ReflectDeepStrict.get(123, ['prop']); // primitive target
License
MIT License