Research
Security News
Quasar RAT Disguised as an npm Package for Detecting Vulnerabilities in Ethereum Smart Contracts
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
@thi.ng/paths
Advanced tools
Immutable, optimized and optionally typed path-based object property / array accessors with structural sharing
This project is part of the @thi.ng/umbrella monorepo.
Immutable, optimized and optionally typed path-based object property / array accessors with structural sharing.
STABLE - used in production
yarn add @thi.ng/paths
Package sizes (gzipped): ESM: 1.0KB / CJS: 1.1KB / UMD: 1.1KB
Several demos in this repo's /examples directory are using this package.
A selection:
Using hdom in an Elm-like manner
Minimal demo of using rstream constructs to form an interceptor-style event loop
The getter()
, setter()
and updater()
functions compile a lookup
path like a.b.c
into an optimized function operating directly at the
value the path points to in nested object. For getters, this essentially
compiles to val = obj.a.b.c
, with the important difference that the
function returns undefined
if any intermediate values along the lookup
path are undefined (and doesn't throw an error).
The resulting setter function too accepts a single object (or array) to operate on and when called, immutably replaces the value at the given path, i.e. it produces a selective deep copy of obj up until given path. If any intermediate key is not present in the given object, it creates a plain empty object for that missing key and descends further along the path.
s = setter("a.b.c");
// or
s = setter(["a","b","c"]);
s({a: {b: {c: 23}}}, 24)
// {a: {b: {c: 24}}}
s({x: 23}, 24)
// { x: 23, a: { b: { c: 24 } } }
s(null, 24)
// { a: { b: { c: 24 } } }
Nested value updaters follow a similar pattern, but also take a user supplied function to apply to the existing value (incl. any other arguments passed):
inc = updater("a.b", (x) => x != null ? x + 1 : 1);
inc({a: {b: 10}});
// { a: { b: 11 } }
inc({});
// { a: { b: 1 } }
// with additional arguments
add = updater("a.b", (x, n) => x + n);
add({a: {b: 10}}, 13);
// { a: { b: 23 } }
In addition to these higher-order functions, the module also provides
immediate-use wrappers: getIn()
, setIn()
, updateIn()
and
deleteIn()
. These functions are using getter
/ setter
internally,
so have same behaviors.
state = {a: {b: {c: 23}}};
getIn(state, "a.b.c")
// 23
setIn(state, "a.b.c", 24)
// {a: {b: {c: 24}}}
// apply given function to path value
updateIn(state, "a.b.c", x => x + 1)
// {a: {b: {c: 24}}}
// immutably remove path key
deleteIn(state, "a.b.c.")
// {a: {b: {}}}
Since v2.2.0 type checked versions of the above accessors are available:
getterT
/ getInT
setterT
/ setInT
updaterT
/ updateInT
deleteInT
mutatorT
/ mutInT
These functions use generics (via mapped types) to validate the given
path against the type structure of the state object. Since string paths
cannot be type checked, only path tuples are supported. Type checking &
inference supports path lengths up to 8 (i.e. levels of
hierarchy) before reverting back to any
.
const state = { a: { b: 1, c: ["c1", "c2"] } };
const b = getInT(state, ["a", "b"]); // b inferred as number
const c = getInT(state, ["a", "c"]); // c inferred as string[]
const c1len = getInT(state, ["a", "c", 0, "length"]); // inferred as number
getIn(state, ["a", "d"]); // compile error
getIn(state, ["x"]); // compile error
Using the typed checked HOF versions (e.g. getterT
, setterT
etc.) is
slightly more verbose due to missing type information of the not yet
know state and the way generics are done in TypeScript:
// define state structure (see above example)
interface State {
a: {
b: number;
c: string[];
}
}
// build typed getter for `b` & `c` state
const getB = getterT<State, "a", "b">(["a", "b"]);
const getFirstC = getterT<State, "a", "c", 0>(["a", "c", 0]);
// using `state` from previous example
const b = getB(state); // inferred as number
const c1 = getFirstC(state); // inferred as string
Since deleteInT
immutably removes a key from the given state object, it also returns a new type from which the key has been explicitly removed.
// again using `state` from above example
// remove nested key `a.c`
const state2 = deleteInT(state, ["a","c"]);
// compile error: "Property `c` does not exist`
state2.a.c;
Only keys in the path will be updated, all other keys present in the
given object retain their original/identical values to provide efficient
structural sharing / re-use. This is the same behavior as in Clojure's
immutable maps or those provided by ImmutableJS (albeit those
implementation are completely different - they're using trees, we're
using the ES6 spread op (for objects, slice()
for arrays) and dynamic
functional composition to produce the setter/updater).
s = setter("a.b.c");
// original
a = { x: { y: { z: 1 } }, u: { v: 2 } };
// updated version
b = s(a, 3);
// { x: { y: { z: 1 } }, u: { v: 2 }, a: { b: { c: 3 } } }
// verify anything under keys `x` & `u` is still identical
a.x === b.x // true
a.x.y === b.x.y // true
a.u === b.u; // true
mutator()
is the mutable alternative to setter()
. It returns a
function, which when called, mutates given object / array at given path
location and bails if any intermediate path values are non-indexable
(only the very last path element can be missing in the actual object
structure). If successful, returns original (mutated) object, else
undefined
. This function too provides optimized versions for path
lengths <= 4.
As with setIn
, mutIn
is the immediate use mutator, i.e. the same as:
mutator(path)(state, val)
.
mutIn({ a: { b: [10, 20] } }, "a.b.1", 23);
// or
mutIn({ a: { b: [10, 20] } }, ["a", "b", 1], 23);
// { a: { b: [ 10, 23 ] } }
// fails (because of missing path structure in target object)
mutIn({}, "a.b.c", 23);
// undefined
The exists()
function takes an arbitrary object and lookup path.
Descends into object along path and returns true if the full path exists
(even if final leaf value is null
or undefined
). Checks are
performed using hasOwnProperty()
.
exists({ a: { b: { c: [null] } } }, "a.b.c.0");
// true
exists({ a: { b: { c: [null] } } }, "a.b.c.1");
// false
Karsten Schmidt
© 2016 - 2020 Karsten Schmidt // Apache Software License 2.0
FAQs
Immutable, optimized and optionally typed path-based object property / array accessors with structural sharing
The npm package @thi.ng/paths receives a total of 2,134 weekly downloads. As such, @thi.ng/paths popularity was classified as popular.
We found that @thi.ng/paths demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
Security News
Research
A supply chain attack on Rspack's npm packages injected cryptomining malware, potentially impacting thousands of developers.
Research
Security News
Socket researchers discovered a malware campaign on npm delivering the Skuld infostealer via typosquatted packages, exposing sensitive data.