Skip to content

munesoft/objx

Repository files navigation

@munesoft/objx — A Modern lodash.get / lodash.set / Deep Merge Alternative

version tests dependencies license node types

A fast, safe, and lightweight utility library for deep object access, deep object mutation, and deep merging in JavaScript and TypeScript — designed as a modern drop-in replacement for lodash.get, lodash.set, and lodash.merge.


Why objx?

Working with deeply nested objects in JavaScript usually means one of two things:

  1. Messy manual chaining that throws on null:

    const name = user && user.profile && user.profile.name; // 😩
  2. Pulling in all of lodash for three utilities:

    const _ = require("lodash");
    _.get(obj, "user.profile.name");   // ~70 KB for 3 functions

objx replaces the most-used 20% of lodash with a focused, typed, 10× better experience — in under 2 KB gzipped.


Features

Feature lodash objx
Deep get
Deep set
Deep merge
Immutable updates
Prototype-pollution safe ⚠️
Typed paths (TypeScript)
Path compiler (hot-path perf)
Tree-shakable
ESM + CJS partial
Bundle size ~70 KB < 2 KB

Installation

npm install @munesoft/objx
# or
yarn add @munesoft/objx
# or
pnpm add @munesoft/objx

Quick Start

import * as objx from "@munesoft/objx";

const user = {
  profile: { name: "Alice", age: 30 },
  tags: ["admin", "editor"],
};

// Deep get — never throws, returns fallback on missing path
objx.get(user, "profile.name");              // "Alice"
objx.get(user, "profile.address.city", "?"); // "?"
objx.get(user, "tags[0]");                   // "admin"

// Deep set — creates missing intermediaries
objx.set(user, "profile.location.city", "Nairobi");

// Immutable deep set — returns new object, original untouched
const next = objx.set(user, "profile.name", "Bob", { immutable: true });
user.profile.name; // still "Alice"

// Deep merge — prototype-pollution safe
const merged = objx.merge({ db: { host: "localhost" } }, { db: { port: 5432 } });
// { db: { host: "localhost", port: 5432 } }

// Check if path exists
objx.has(user, "profile.name");   // true
objx.has(user, "profile.email");  // false

// Delete a nested key
objx.del(user, "profile.age");

// Apply defaults (fills only undefined keys, recursively)
objx.defaults(config, defaultConfig);

API Reference

get(obj, path, fallback?)

Safely retrieve a deeply nested value. Never throws.

objx.get({ a: { b: [1, 2, 3] } }, "a.b[2]");          // 3
objx.get({ a: { b: null } },       "a.b.c", "default"); // "default"
objx.get(null,                     "a.b",   "safe");    // "safe"
  • Supports dot notation: "user.profile.name"
  • Supports bracket notation: "users[0].email"
  • Supports mixed: "data[2].rows[0].value"
  • Returns fallback (or undefined) on missing/null paths

set(obj, path, value, options?)

Set a deeply nested value, auto-creating missing objects and arrays.

// Mutable (default) — modifies obj in place
objx.set({}, "a.b.c", 42);
// { a: { b: { c: 42 } } }

// Immutable — returns a structurally-shared clone
const next = objx.set(state, "user.name", "Alice", { immutable: true });

Options:

Option Type Default Description
immutable boolean false Return new object instead of mutating

merge(target, ...sources, options?)

Deep merge one or more source objects into target. Prototype-pollution safe.

objx.merge({ a: { x: 1 } }, { a: { y: 2 } });
// { a: { x: 1, y: 2 } }

// Concatenate arrays instead of replacing
objx.merge({ tags: ["a"] }, { tags: ["b"] }, { arrays: "concat" });
// { tags: ["a", "b"] }

// Deep-merge array elements
objx.merge(
  { items: [{ id: 1, x: 10 }] },
  { items: [{ id: 1, y: 20 }] },
  { arrays: "merge" }
);
// { items: [{ id: 1, x: 10, y: 20 }] }

Options:

Option Type Default Description
arrays "replace" | "concat" | "merge" "replace" Array merge strategy
mutate boolean false Mutate target directly

has(obj, path)

Check whether a nested path exists on an object.

objx.has({ a: { b: 1 } }, "a.b");    // true
objx.has({ a: { b: 1 } }, "a.c");    // false
objx.has({ a: null },      "a.b");    // false

del(obj, path)

Delete a deeply nested property. Returns true if deleted, false if not found.

const obj = { a: { b: 1, c: 2 } };
objx.del(obj, "a.b"); // true
// obj is now { a: { c: 2 } }

defaults(target, source)

Recursively fill in undefined values in target from source. Like _.defaults but deep.

objx.defaults(
  { db: { host: "localhost" } },
  { db: { host: "remote", port: 5432 }, timeout: 3000 }
);
// { db: { host: "localhost", port: 5432 }, timeout: 3000 }

compile(path) — Path Compiler

Pre-parse a path into a reusable getter. Eliminates path-parsing overhead in hot loops.

const getName = objx.compile("user.profile.name");

// In a tight loop — no string re-parsing on each call
users.map(getName); // fast!

// With fallback
getName(obj, "anonymous");

Real-World Use Cases

API request handling

import { get } from "@munesoft/objx";

app.post("/login", (req, res) => {
  const userId   = get(req, "body.user.id");
  const authType = get(req, "headers.x-auth-type", "jwt");
  const scopes   = get(req, "body.scopes[0]", "read");
});

Configuration management

import { defaults, get } from "@munesoft/objx";

const config = loadUserConfig();
const DEFAULTS = {
  db:     { host: "localhost", port: 5432, pool: 10 },
  cache:  { ttl: 300, max: 1000 },
  logger: { level: "info" },
};

defaults(config, DEFAULTS);

const dbHost = get(config, "db.host");

Immutable state management (Redux-like)

import { set, get } from "@munesoft/objx";

function reducer(state, action) {
  switch (action.type) {
    case "SET_USER_NAME":
      return set(state, "user.name", action.payload, { immutable: true });
    case "INCREMENT_SCORE":
      const current = get(state, "game.score", 0);
      return set(state, "game.score", current + 1, { immutable: true });
    default:
      return state;
  }
}

Data transformation pipelines

import { compile, merge } from "@munesoft/objx";

// Compile hot-path getters once
const getId    = compile("data.id");
const getEmail = compile("contact.email");
const getName  = compile("profile.name");

// Transform thousands of records efficiently
const normalized = rawRecords.map((rec) =>
  merge({}, {
    id:    getId(rec),
    email: getEmail(rec),
    name:  getName(rec, "Unknown"),
  })
);

Security

objx blocks prototype-pollution attacks by design. The following keys are silently ignored across all operations:

  • __proto__
  • constructor
  • prototype
// This JSON payload cannot pollute Object.prototype
const malicious = JSON.parse('{"__proto__":{"isAdmin":true}}');
objx.merge({}, malicious);
({}).isAdmin; // undefined ✅

Tree Shaking

Import only what you need:

import { get } from "@munesoft/objx/get";
import { set } from "@munesoft/objx/set";
import { merge } from "@munesoft/objx/merge";

TypeScript

Full TypeScript support with strict types:

import { get, set, compile, merge } from "@munesoft/objx";
import type { MergeOptions, SetOptions } from "@munesoft/objx";

// Generic return types
const name = get<string>(obj, "user.name", "anon");
//    ^? string

// Typed compiled getter
const getScore = compile<number>("game.score");
const score = getScore(state, 0);
//    ^? number | undefined

Performance

objx is optimised for speed:

  • Path cache — paths are parsed once, then cached (LRU-capped at 512 entries).
  • compile() — for hot loops, bypass per-call parsing entirely.
  • Minimal allocations — immutable mode uses structural sharing.
  • No dependencies — zero runtime overhead.

Related searches

  • lodash get alternative
  • lodash set alternative
  • lodash merge alternative
  • deep object access javascript
  • deep merge nodejs
  • immutable object update js
  • nested object path javascript
  • safe nested access typescript
  • object path utility npm
  • prototype pollution safe merge

GitHub Topics

lodash · javascript · typescript · object-utils · deep-merge · immutable · deep-get · deep-set · lodash-alternative · npm


License

MIT © munesoft

About

Fast, safe lodash.get / lodash.set / deep merge alternative for JavaScript & TypeScript — immutable updates, zero dependencies, prototype-pollution safe, tree-shakable ESM + CJS

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors