dotpathkit
v0.1.1
Published
Tiny, type-safe deep get/set by dot-path — immutable set/unset, prototype-pollution safe, array-index syntax. Zero dependencies.
Maintainers
Readme
dotpathkit
Tiny, type-safe deep get / set by dot-path — immutable
set/unset, prototype-pollution safe, array-index syntax. Zero dependencies.
lodash.get is great until you reach for lodash.set and it mutates your state,
or a user-supplied path writes through __proto__. dotpathkit keeps the
ergonomic "a.b[0].c" access and makes writes immutable and
pollution-safe — perfect for reducers, config patches, and anywhere you read
a path that came from outside. Zero dependencies.
import { get, set } from "dotpathkit";
get({ a: { b: [{ c: 1 }] } }, "a.b[0].c"); // 1
const next = set(state, "user.address.zip", "94107");
// new object; `state` is untouchedWhy dotpathkit?
- Read safely.
get(obj, path, default?)returns the default for any missing segment;has(obj, path)distinguishes "missing" from "present butundefined". - Write immutably.
setandunsetreturn a new structure and reuse untouched branches by reference (structural sharing) — ideal for state updates. - Pollution-safe. Writes through
__proto__/constructor/prototypeare refused, so an attacker-controlled path can't poisonObject.prototype. - Flexible paths.
"a.b.c","a[0].b",'a["x.y"].z', or an array["a", 0, "b"]. Numeric segments create arrays. - Typed & tiny. Full types, ESM + CJS, zero dependencies.
Install
npm install dotpathkit
# or: pnpm add dotpathkit / yarn add dotpathkit / bun add dotpathkitReading
import { get, has } from "dotpathkit";
const data = { user: { roles: ["admin"], meta: { age: null } } };
get(data, "user.roles[0]"); // "admin"
get(data, "user.email", "n/a"); // "n/a"
get(data, ["user", "roles", 0]); // "admin" (array path)
has(data, "user.meta.age"); // true (value is null, but present)
has(data, "user.meta.name"); // falseWriting (immutable)
import { set, unset } from "dotpathkit";
const state = { a: { b: 1 }, list: [{ id: 1 }] };
set(state, "a.c", 2); // { a: { b: 1, c: 2 }, list: [...] }
set(state, "list[0].id", 99); // copies list & element 0; siblings reused
set({}, "x.y.z", 1); // { x: { y: { z: 1 } } } — creates the path
set({}, "rows[0].cols[1]", 5); // { rows: [ { cols: [ <empty>, 5 ] } ] }
unset(state, "a.b"); // { a: {}, list: [...] }
unset(state, "list[0]"); // array spliced & re-indexedstate is never modified — each call returns a fresh structure that shares the
parts you didn't touch. If unset targets a path that doesn't exist, the
original reference is returned (no needless copy).
Notes
- A numeric path segment (
0,[0]) creates an array when building a path; otherwise an object is created. set/unsetperform structural sharing — only the nodes along the path are copied.
Pairs well with
| Need | Use |
| --- | --- |
| Deep merge whole objects | @billdaddy/mergekit |
| Deep equal / clone | equalkit |
| Rewrite object keys' case | casekit |
Contributors ✨
This project follows the all-contributors specification. Contributions of any kind are welcome — code, docs, bug reports, ideas, reviews! See the emoji key for how each contribution is recognized, and open a PR or issue to get involved.
Thanks goes to these wonderful people:
License
MIT © Tung Tran
