A lightweight, zero-dependency TypeScript library for flattening, transforming, picking, and diffing deeply nested JSON objects.
Working with deeply nested API responses in JavaScript is painful. Libraries like lodash solve it, but bring hundreds of KB you don't need. flatmap-json is a focused toolkit — does exactly one job, does it well, ships in under 5 KB.
npm install flatmap-json
# or
yarn add flatmap-json
# or
pnpm add flatmap-jsonimport { flatten, unflatten, pick, diff, transform } from 'flatmap-json'
// Flatten a nested object
flatten({ user: { name: 'Arjun', address: { city: 'Bengaluru' } } })
// → { "user.name": "Arjun", "user.address.city": "Bengaluru" }
// Restore it
unflatten({ "user.name": "Arjun", "user.address.city": "Bengaluru" })
// → { user: { name: "Arjun", address: { city: "Bengaluru" } } }Converts a deeply nested object into a single-level object with delimited keys.
flatten({ a: { b: 1 } }) // { "a.b": 1 }
flatten({ a: { b: 1 } }, '/') // { "a/b": 1 } ← shorthand
flatten({ a: { b: 1 } }, { delimiter: '_' }) // { "a_b": 1 }
flatten({ a: [1, 2] }, { flattenArrays: false }) // { a: "[1,2]" }
flatten({ a: { b: { c: 1 } } }, { maxDepth: 1 }) // stops at depth 1Options:
| Option | Type | Default | Description |
|---|---|---|---|
delimiter |
string |
'.' |
Separator between key segments |
maxDepth |
number |
Infinity |
Maximum nesting depth to flatten |
flattenArrays |
boolean |
true |
Whether to flatten array indices |
Restores a flat delimited-key object back into a nested structure.
unflatten({ "a.b": 1 }) // { a: { b: 1 } }
unflatten({ "a/b": 1 }, '/') // { a: { b: 1 } }Selects specific deep paths from an object. Paths use dot notation.
const obj = { a: { b: 1, c: 2 }, d: 3 }
pick(obj, ['a.b', 'd']) // { a: { b: 1 }, d: 3 }
pick(obj, ['a.b']) // { a: { b: 1 } }
pick(obj, ['x.y.z']) // {} (missing paths are silently ignored)Returns a new object excluding the given deep paths. Does not mutate the original.
const obj = { a: { b: 1, c: 2 }, d: 3 }
omit(obj, ['a.b']) // { a: { c: 2 }, d: 3 }
omit(obj, ['d']) // { a: { b: 1, c: 2 } }Recursively maps over all leaf (primitive) values of the object. The transform function receives (value, key, fullPath).
// Double all numbers
transform({ a: 1, b: { c: 2 } }, (v) => typeof v === 'number' ? v * 2 : v)
// → { a: 2, b: { c: 4 } }
// Uppercase all strings
transform({ name: 'arjun' }, (v) => typeof v === 'string' ? v.toUpperCase() : v)
// → { name: 'ARJUN' }
// Use the path argument to selectively transform
transform(obj, (v, key, path) => {
if (path.startsWith('user.')) return String(v)
return v
})Recursively renames all keys using a mapping function.
renameKeys({ firstName: 'Arjun' }, (k) => k.replace(/([A-Z])/g, '_$1').toLowerCase())
// → { first_name: 'Arjun' }Returns an object containing only the keys that changed, were added, or removed — with { from, to } pairs.
diff(
{ user: { name: 'Arjun', age: 28 } },
{ user: { name: 'Arjun', age: 29 } }
)
// → { "user.age": { from: 28, to: 29 } }
diff({ a: 1 }, { a: 1, b: 2 })
// → { b: { from: undefined, to: 2 } }Deep equality check using diff under the hood.
isEqual({ a: { b: 1 } }, { a: { b: 1 } }) // true
isEqual({ a: 1 }, { a: 2 }) // falseDeep merge — values in objB win on conflict. Neither input is mutated.
merge(
{ a: { b: 1, c: 2 } },
{ a: { c: 99, d: 3 } }
)
// → { a: { b: 1, c: 99, d: 3 } }All functions are fully typed. Key types are exported:
import type {
JsonObject,
JsonValue,
JsonPrimitive,
FlatObject,
DiffResult,
FlattenOptions,
TransformFn,
} from 'flatmap-json'| Export | Size (minified + gzipped) |
|---|---|
flatten + unflatten |
~900 B |
pick + omit |
~600 B |
transform + renameKeys |
~500 B |
diff + isEqual + merge |
~700 B |
| Full library | ~2.4 KB |
PRs and issues welcome! See CONTRIBUTING.md for guidelines.
git clone https://github.com/yourusername/flatmap-json
cd flatmap-json
npm install
npm test # run tests
npm run build # build to /distMIT