| name | stacks-objects |
| description | Use when working with object manipulation in Stacks — deep merging with type safety, object mapping/transformation, strict key checking, typed entries/keys, property picking, clearing undefined values, or the DeepMerge utility type. Covers @stacksjs/objects. |
| license | MIT |
| compatibility | Bun >= 1.3.0, TypeScript |
| allowed-tools | Read Edit Write Bash Grep Glob |
Stacks Object Utilities
Type-safe object manipulation helpers. All functions are exported from a single file.
Key Path
- Core package:
storage/framework/core/objects/src/index.ts (single file, all functions)
Dependencies
@stacksjs/types -- DeepMerge type
@stacksjs/utils -- notNullish filter function
@stacksjs/validation -- isObject type guard
Functions
objectMap -- Transform Object Key/Value Pairs
function objectMap<K extends string, V, NK = K, NV = V>(
obj: Record<K, V>,
fn: (key: K, value: V) => [NK, NV] | undefined,
): Record<K, V>
The callback receives (key, value) and must return either:
[newKey, newValue] tuple to include the entry
undefined to filter out the entry
Implementation uses Object.fromEntries(Object.entries(obj).map(...).filter(notNullish)).
objectMap({ a: 1, b: 2 }, (k, v) => [k, v * 2])
objectMap({ a: 1, b: 2 }, (k, v) => [`prefix_${k}`, v])
objectMap({ a: 1, b: 2 }, (k, v) => [v, k])
objectMap({ a: 1, b: 2, c: 3 }, (k, v) => k === 'b' ? undefined : [k, v])
deepMerge -- Deep Merge Objects
function deepMerge<T extends object = object, S extends object = T>(
target: T,
...sources: S[]
): DeepMerge<T, S>
Recursively merges source objects into target. Handles multiple sources via recursive spread.
Key behaviors:
- Only merges "mergeable objects" (objects that are not arrays, checked via
isObject() and !Array.isArray())
- Arrays are NOT deeply merged -- they are replaced entirely
- Missing keys in target are created as empty objects
{} before recursive merge
- Mutates the target object -- the return value IS the modified target
- Processes sources left to right
const target = { a: 1, nested: { x: 1, y: 2 } }
const source = { b: 2, nested: { y: 3, z: 4 } }
const result = deepMerge(target, source)
const merged = deepMerge(defaults, userConfig, overrides)
The DeepMerge<T, S> type (from @stacksjs/types) provides full type inference:
type DeepMerge<F, S> = MergeInsertions<{
[K in keyof F | keyof S]: K extends keyof S & keyof F
? DeepMerge<F[K], S[K]>
: K extends keyof S ? S[K]
: K extends keyof F ? F[K]
: never
}>
objectPick -- Pick Properties by Key
function objectPick<O extends object, T extends keyof O>(
obj: O,
keys: T[],
omitUndefined?: boolean,
): Pick<O, T>
Creates a new object with only the specified keys. Uses reduce internally.
const user = { id: 1, name: 'John', email: 'john@test.com', password: 'secret' }
objectPick(user, ['id', 'name', 'email'])
const partial = { id: 1, name: 'John', bio: undefined }
objectPick(partial, ['id', 'name', 'bio'], true)
- Only includes keys that exist in the object (
k in obj check)
- With
omitUndefined=true, additionally skips keys where obj[k] === undefined
clearUndefined -- Remove Undefined Fields
function clearUndefined<T extends object>(obj: T): T
Mutates the object by deleting keys whose values are undefined. Returns the same object reference.
const data = { name: 'John', age: undefined, email: 'john@test.com' }
clearUndefined(data)
- Only removes
undefined values -- null, 0, '', false are preserved
- Uses
delete obj[key] for removal
isKeyOf -- Type Guard for Object Keys
function isKeyOf<T extends object>(obj: T, k: keyof any): k is keyof T
Type guard that narrows k to keyof T if the key exists in obj. Uses k in obj.
const config = { debug: true, verbose: false }
const key: string = 'debug'
if (isKeyOf(config, key)) {
console.log(config[key])
}
objectKeys -- Strictly Typed Object.keys
function objectKeys<T extends object>(obj: T): Array<`${keyof T & (string | number | boolean | null | undefined)}`>
Returns Object.keys(obj) with a stricter return type than the built-in Object.keys() (which returns string[]).
const user = { name: 'John', age: 30 }
const keys = objectKeys(user)
objectEntries -- Strictly Typed Object.entries
function objectEntries<T extends object>(obj: T): Array<[keyof T, T[keyof T]]>
Returns Object.entries(obj) with a stricter return type than the built-in Object.entries() (which returns [string, any][]).
const user = { name: 'John', age: 30 }
const entries = objectEntries(user)
hasOwnProperty -- Safe Property Check
function hasOwnProperty<T>(obj: T, v: PropertyKey): boolean
Null-safe wrapper around Object.prototype.hasOwnProperty.call(). Returns false if obj is null or undefined.
hasOwnProperty({ a: 1 }, 'a')
hasOwnProperty({ a: 1 }, 'b')
hasOwnProperty(null, 'a')
hasOwnProperty(undefined, 'a')
Gotchas
deepMerge mutates the target object -- it does not create a new object. The return value is the same reference as target.
clearUndefined mutates the input object -- it deletes properties in place.
objectPick creates a NEW object -- original is not affected.
objectMap creates a NEW object via Object.fromEntries.
deepMerge does NOT deeply merge arrays -- arrays from source replace arrays in target entirely.
deepMerge uses isObject() from @stacksjs/validation combined with !Array.isArray() to determine mergeability.
objectMap can filter entries by returning undefined from the callback -- filtered via notNullish from @stacksjs/utils.
objectKeys return type uses a template literal type to handle non-string keys.
clearUndefined only removes undefined -- null values are preserved.
hasOwnProperty accepts null/undefined objects without throwing.
objectPick with omitUndefined=false (default) includes keys even if their values are undefined, as long as the key exists in the object.
- These utilities are also re-exported through
@stacksjs/utils.