Objects
Path-aware reads/writes, deep transforms, structural diff. Import from @mongez/reinforcements.
Path access — get / set / has / unset
get
get<T, P extends Path<T>>(obj: T, path: P, default?): PathValue<T, P>get<T = any>(obj: any, path: string, default?: T): TRead by typed dot-notation. Falsy values pass through correctly (no spurious default-substitution on 0 / "" / false).
get({ user: { email: "ada@x.com" } }, "user.email"); // "ada@x.com"get({ user: {} }, "user.email", "n/a"); // "n/a"get(arr, "0.name"); // numeric segments index arraysset
set<T>(obj: T, path: string, value: unknown): TMutating write by dot-notation. Auto-creates arrays when the next segment is a numeric index.
set({}, "users.0.name", "Ada"); // { users: [{ name: "Ada" }] }set(obj, "a.b.c", 1); // creates a.b.c chainhas
has(obj: any, path: string): booleantrue if the path exists, even when the value is undefined. Use this to distinguish “missing” from “present-but-undefined”.
has({ a: { b: 1 } }, "a.b"); // truehas({ a: { b: undefined } }, "a.b"); // truehas({ a: {} }, "a.b"); // falseunset
unset<T>(obj: T, paths: readonly string[]): TMutating remove by dot-notation. Returns the same reference.
unset({ a: 1, b: 2 }, ["a"]); // { b: 2 }unset({ a: { b: 1, c: 2 } }, ["a.b"]); // { a: { c: 2 } }Key selection — pick / omit
pick
pick<T, K extends keyof T>(obj: T, keys: readonly K[]): Pick<T, K>pick(obj, keys: string[]): Record<string, any> // supports dot-notation pathspick(obj, predicate: (value, key) => boolean): Record<string, any>pick({ a: 1, b: 2, c: 3 }, ["a", "c"]); // { a: 1, c: 3 }pick({ a: { b: 1, c: 2 } }, ["a.b"]); // { a: { b: 1 } }pick({ a: 1, b: 2, c: 3 }, v => v > 1); // { b: 2, c: 3 }omit
omit<T, K extends keyof T>(obj: T, keys: readonly K[]): Omit<T, K>omit(obj, keys: string[]): Record<string, any> // supports dot-notation pathsomit(obj, predicate: (value, key) => boolean): Record<string, any>Non-mutating; returns a shallow clone minus the keys/paths.
omit({ a: 1, b: 2 }, ["b"]); // { a: 1 }omit({ a: { b: 1, c: 2 } }, ["a.b"]); // { a: { c: 2 } }omit({ a: 1, b: 2 }, (_, k) => k === "a"); // { b: 2 }
onlyandexceptare kept as@deprecatedaliases ofpickandomit— prefer the new names.
Cleanup — compact
compact
compact<T extends Record<string, any>>(obj: T, options?: CompactOptions): Partial<T>compact<T>(array: T[], options?: CompactOptions): T[]
type CompactOptions = { predicate?: (value: any) => boolean; // default: nullish or "" empties?: boolean; // default: true — drop [] and {} too deep?: boolean; // default: true};Strip “empty” entries from objects/arrays. Default predicate drops null, undefined, and "" only — keeps 0, false, NaN because those are usually meaningful. With empties and deep on by default, parent containers that become empty after recursion are themselves dropped.
compact({ name: "Ada", email: "", phone: null, age: 0 });// { name: "Ada", age: 0 }
compact({ user: { name: "Ada", email: "" }, meta: {} });// { user: { name: "Ada" } }
compact(["a", "", null, "b"]);// ["a", "b"]
compact({ a: 0, b: -1 }, { predicate: v => v === -1 });// { a: 0 }
compact({ tags: [], name: "Ada" }, { empties: false });// { tags: [], name: "Ada" }Typical uses: cleaning API request payloads, building query strings from filter objects, sanitizing form data.
Deep transforms — merge / clone / flatten / freeze
merge
merge<A, B>(a: A, b: B): A & Bmerge(...sources, options?: { arrays?: "replace" | "concat" | "union" }): anyRecursive merge of plain objects. Arrays default to replace; pass an options object as the last argument to change strategy.
merge({ a: { b: 1 } }, { a: { c: 2 } });// { a: { b: 1, c: 2 } }
merge({ list: [1, 2] }, { list: [3, 4] }, { arrays: "concat" });// { list: [1, 2, 3, 4] }
merge({ list: [1, 2] }, { list: [2, 3] }, { arrays: "union" });// { list: [1, 2, 3] }Class instances are taken from the latest source rather than merged (cloning custom constructors is out of scope).
clone
clone<T>(value: T): TDeep clone. Handles Date, RegExp, Error (with own props), Map, Set, typed arrays, ArrayBuffer, and circular references via internal WeakMap. Non-plain class instances are returned by reference.
const copy = clone({ user: { name: "Ada" }, tags: ["x"] });copy.user.name = "Bob";// original is untouchedflatten
flatten(obj: any, options?: { separator?: string; // default "." keepNested?: boolean; // default false maxDepth?: number; // default Infinity}): Record<string, any>Flatten to a single-level map of dot-keyed paths. Descends into plain objects, arrays, and class instances. Treats Date / RegExp / Map / Set / typed arrays as leaves.
flatten({ a: { b: 1, c: [2, 3] } });// { "a.b": 1, "a.c.0": 2, "a.c.1": 3 }
flatten({ a: { b: 1 } }, { separator: "/" });// { "a/b": 1 }freeze
freeze<T>(value: T): Readonly<T>Recursive Object.freeze across plain objects and arrays.
const config = freeze({ api: { url: "..." } });config.api.url = "x"; // throws in strict modeDefaults & inversion
defaults
defaults<T>(target: T, ...sources): TMutating: fills only undefined keys on target from each source, left to right.
defaults({ a: 1 }, { a: 2, b: 3 }); // { a: 1, b: 3 }defaults({}, { a: 1 }, { a: 2 }); // { a: 1 } (first source wins)invert
invert<K, V>(obj: Record<K, V>): Record<string, K>Swap keys and values; values are coerced to strings; duplicate values collide last-wins.
invert({ a: 1, b: 2 }); // { "1": "a", "2": "b" }Per-entry mapping
mapValues / mapKeys
mapValues<T, U>(obj: T, fn: (value, key, obj) => U): Record<keyof T & string, U>mapKeys<T>(obj: T, fn: (key, value, obj) => string): Record<string, T[keyof T]>mapValues({ a: 1, b: 2 }, v => v * 2); // { a: 2, b: 4 }mapKeys({ a: 1, b: 2 }, k => k.toUpperCase()); // { A: 1, B: 2 }map
map<T, U>(obj: T, fn: (key, value, obj) => U): U[]Map an object to an array.
map({ a: 1, b: 2 }, (k, v) => `${k}=${v}`); // ["a=1", "b=2"]Typed enumeration
keys<T>(obj: T): Array<keyof T & string>values<T>(obj: T): Array<T[keyof T]>entries<T>(obj: T): Array<[keyof T & string, T[keyof T]]>fromEntries<K, V>(entries: Iterable<readonly [K, V]>): Record<K, V>Typed wrappers around the matching Object.* static methods.
Traversal & comparison
walk
walk(obj: any, visitor: (value, path, parent, key) => void, parentPath?: string): voidRecursive leaf traversal. Descends into plain objects and arrays; calls visitor for every non-container value with the full dot-path.
walk({ a: { b: 1, c: [2, 3] } }, (value, path) => log(path, value));// "a.b" 1// "a.c.0" 2// "a.c.1" 3diff
diff(a: object, b: object): { added: Record<string, any>; removed: Record<string, any>; changed: Record<string, { from: any; to: any }>;}Shallow structural diff; uses deep value equality (areEqual) for nested comparison.
diff({ a: 1, b: 2 }, { a: 1, b: 3, c: 4 });// { added: { c: 4 }, removed: {}, changed: { b: { from: 2, to: 3 } } }Sorting
sort
sort<T>(obj: T, recursive?: boolean): T // default recursive = trueReturn a new object with keys sorted alphabetically. Arrays of objects are out of scope — use @mongez/collection sortBy.
sort({ b: 1, a: 2, c: 3 }); // { a: 2, b: 1, c: 3 }sort({ b: { y: 1, x: 2 }, a: 1 }); // recursive by default