json-reduce
Reduce any JSON value depth-first with subtree-skipping support, for TypeScript and JavaScript.
Repository Health
Technical Analysis
json-reduce brings Array.prototype.reduce-style ergonomics to arbitrary JSON values. Instead of writing a bespoke recursive walker every time you need to sum, collect, or transform data buried inside nested objects and arrays, you hand json-reduce a single reducer function and it visits every node depth-first, passing along the accumulator, the current value, and a dot-path describing where in the tree that value lives.
The library adds one feature plain recursion doesn’t give you for free: selective subtree skipping. Returning the exported SKIP marker (or calling it with a value) from inside the reducer stops traversal from descending into that node’s children, which is useful for early termination, ignoring irrelevant branches, or avoiding unnecessary work on large documents. A createPathSegment hook also lets callers customize how path segments are built, rather than always getting raw object keys and array indices.
It’s a small, dependency-free TypeScript utility aimed at anyone who repeatedly writes ad hoc tree-walking code over JSON-shaped data — config objects, API responses, or document trees — and would rather express that traversal as a single reducer function.
What You Get
- Depth-first traversal - every node (objects, arrays, and primitives) in a JSON value is visited exactly once, parent-before-children.
- Reducer-based API - a single
(accumulator, value, path) => nextAccumulatorfunction replaces hand-written recursive walkers. - Dot-path tracking - each invocation receives the path (array of keys/indices) to the current node, so consumers know exactly where a value lives.
- Subtree skipping via
SKIP- returningSKIPorSKIP(value)from the reducer stops traversal into that node’s children. - Custom path segment builder - an optional
createPathSegmentcallback lets callers control how path entries are constructed instead of using raw keys/indices.
Common Use Cases
- Aggregating scattered values - summing, counting, or collecting every value of a given type nested anywhere inside a JSON document.
- Selective document transforms - rewriting specific nodes (e.g. uppercasing strings) while leaving the rest of the structure untouched.
- Pruned traversal for performance - skipping large or irrelevant subtrees (e.g. already-processed branches) to avoid walking an entire document.
- Path-aware validation or indexing - building a flat index of values keyed by their dot-path location within a nested config or API response.
Under The Hood
Architecture
The library is a single-module implementation (src/reduce.ts) built around one dispatch function, reduceAny, which inspects the runtime type of each value (getType) and routes it to reduceObject, reduceArray, or reducePrimitive. Object and array handling both call the reducer on the container itself before recursing into Object.keys() or the array’s own reduce, threading the accumulator and an appended path array through each recursive call; a shared callReducer helper normalizes the reducer’s return value into a [skip, nextAccumulator] tuple so the SKIP marker (bare or value-carrying) is handled identically everywhere it can appear. There is no internal mutable state, no classes, and no external dependencies — the entire traversal is expressed as pure, path-threading recursion, so the only thing that would break if the core abstraction changed is the shape of that tuple contract between the three node-type reducers.
Tech Stack
The project is authored in TypeScript and compiled with a Babel toolchain (@babel/cli, @babel/preset-env, @babel/preset-typescript) rather than tsc for emit, with tsc --emitDeclarationOnly used separately just to produce .d.ts files. Tests run under Jest with ts-jest/babel-jest, linting is handled by ESLint scoped to src, and formatting by Prettier. There are zero runtime dependencies declared in package.json — the published package is pure TypeScript-compiled-to-JS with generated type declarations.
Code Quality
The project has real test coverage: three Jest test files (reduce.test.ts, custom-path.test.ts, readme-examples.test.ts) exercising the skip behavior, path-collection behavior, custom path segments, and the README’s own documented examples, plus a type-check script run as part of npm test. Naming is generally clear (reduceObject, reduceArray, reducePrimitive, callReducer), but type safety is inconsistent — several @ts-ignore comments and untyped parameters (e.g. getType(value), defaultCreatePathSegment) sit alongside otherwise well-typed generics like Reducer<T> and SkipTuple<T>. There is no CI configuration visible in the cloned repository, so linting and tests appear to run only via local npm scripts.
API Design
The public surface is intentionally tiny: one reduce function plus the SKIP helper, mirroring the shape of Array.prototype.reduce closely enough that JavaScript developers can pick it up with almost no new vocabulary. The one deliberate divergence — requiring an explicit initial value rather than making it optional — is called out directly in the README’s “Gotchas” section, which also documents circular-reference and non-JSON-type limitations up front rather than leaving them to be discovered. The skip mechanism is the only non-obvious piece of the API, and it’s demonstrated with worked examples for both the bare SKIP and value-carrying SKIP(value) forms, keeping the boilerplate needed to get started to a single import and a one-line reducer.