List nested key paths from JavaScript objects and arrays.
object-key-paths is a small TypeScript utility for JSON tooling, admin dashboards, docs generators, table builders, import/export mapping and schema inspection. It is a modern, typed alternative to older deep key listing packages.
Use it when you need to inspect an unknown object shape, build a field picker, generate table columns, document API payloads or collect all leaf values from a JSON-like object.
- TypeScript types are generated from the source.
- ESM-only package with no runtime dependencies.
- Marked as side-effect free for bundlers.
- CI runs
npm ci,typecheck,build, andtest. - Tested on Node.js 20 and 22 with GitHub Actions.
- Handles arrays, empty containers and circular references deliberately.
npm install object-key-pathsimport { getKeyPaths, getLeafPaths, getPathEntries } from 'object-key-paths';
const data = {
user: {
name: 'Ada',
address: {
city: 'Paris'
}
},
tags: ['admin', 'ops']
};
getKeyPaths(data);
// ['user', 'user.name', 'user.address', 'user.address.city', 'tags', 'tags.0', 'tags.1']
getLeafPaths(data);
// ['user.name', 'user.address.city', 'tags.0', 'tags.1']
getPathEntries(data);
// [
// { path: 'user', segments: ['user'], value: { ... }, depth: 1, isLeaf: false, isCircular: false },
// { path: 'user.name', segments: ['user', 'name'], value: 'Ada', depth: 2, isLeaf: true, isCircular: false },
// ...
// ]| Function | Use it when you need |
|---|---|
getKeyPaths |
formatted paths for every reachable container and leaf |
getLeafPaths |
formatted paths only for final values |
getKeySegments |
raw path segments such as ['users', 0, 'name'] |
getPathEntries |
paths plus values, depth and leaf/circular metadata |
formatDotPath |
to format your own segment array as user.name |
formatBracketPath |
to format your own segment array as users[0].name |
If keys may contain dots, prefer pathStyle: 'bracket' or getKeySegments. Dot paths are compact and readable, but dotted keys must be escaped.
Returns string paths for both intermediate containers and leaves.
getKeyPaths({
order: {
customer: {
city: 'Paris'
}
}
});
// ['order', 'order.customer', 'order.customer.city']Returns only paths that point to leaves. Empty objects and empty arrays are treated as leaves.
getLeafPaths({
order: {
id: 'A-001',
lines: []
}
});
// ['order.id', 'order.lines']Returns paths as segment arrays instead of formatted strings.
getKeySegments({ users: [{ name: 'Ada' }] });
// [['users'], ['users', 0], ['users', 0, 'name']]Returns metadata for each path.
const [entry] = getPathEntries({ user: { name: 'Ada' } });
entry;
// {
// path: 'user',
// segments: ['user'],
// value: { name: 'Ada' },
// depth: 1,
// isLeaf: false,
// isCircular: false
// }Formats a segment array as a dot path. Separators and backslashes inside keys are escaped.
formatDotPath(['user.profile', 'name']);
// 'user\\.profile.name'Formats a segment array as a bracket path.
formatBracketPath(['users', 0, 'full.name']);
// 'users[0]["full.name"]'Defaults are intentionally broad: plain objects and arrays are traversed, intermediate containers and leaves are included, and circular references are reported as leaves instead of followed.
interface KeyPathOptions {
includeIntermediate?: boolean;
includeLeaves?: boolean;
includeArrays?: boolean;
includeRoot?: boolean;
maxDepth?: number;
limit?: number;
pathStyle?: 'dot' | 'bracket';
separator?: string;
onCircular?: 'skip' | 'throw';
isTraversable?: (value: unknown, context: TraversalContext) => boolean;
}| Option | Default | Meaning |
|---|---|---|
includeIntermediate |
true |
include containers like user and user.address |
includeLeaves |
true |
include primitive values and empty containers |
includeArrays |
true |
walk arrays and include numeric indexes |
includeRoot |
false |
include the root value as an empty path |
maxDepth |
unlimited | stop traversal after this many path segments |
limit |
unlimited | stop after this many returned entries |
pathStyle |
'dot' |
return dot paths or bracket paths |
separator |
'.' |
separator used by dot paths |
onCircular |
'skip' |
mark circular values as leaves, or throw |
isTraversable |
built-in | override which values should be walked into |
Use bracket paths when keys may contain dots or when the output should be compatible with JavaScript-like path notation.
getLeafPaths(
{
users: [{ 'full.name': 'Ada' }]
},
{ pathStyle: 'bracket' }
);
// ['users[0]["full.name"]']Dot paths escape separators and backslashes:
getLeafPaths({ 'user.profile': { name: 'Ada' } });
// ['user\\.profile.name']Stop traversal after a fixed number of path segments.
getKeyPaths({ a: { b: { c: 1 } } }, { maxDepth: 2 });
// ['a', 'a.b']Keep exploratory scans bounded when payloads are very large:
getKeyPaths(largePayload, { limit: 100 });Disable array traversal when arrays should be treated as values.
getLeafPaths({ tags: ['admin', 'ops'] }, { includeArrays: false });
// ['tags']Circular references are included as leaf entries and not descended into by default.
const data: { self?: unknown } = {};
data.self = data;
getPathEntries(data);
// [{ path: 'self', segments: ['self'], value: data, depth: 1, isLeaf: true, isCircular: true }]Use { onCircular: 'throw' } when circular data should fail fast.
Use with object-path-kit to inspect a JSON object and then safely read values from the discovered paths. Bracket paths are the safest format when object keys may contain dots:
import { getLeafPaths } from 'object-key-paths';
import { getPath } from 'object-path-kit';
const paths = getLeafPaths(report, {
pathStyle: 'bracket'
});
const values = paths.map((path) => ({
path,
value: getPath(report, path)
}));Use with array-table-kit to turn discovered paths into a Markdown inventory:
import { arrayToMarkdownTable } from 'array-table-kit';
import { getPathEntries } from 'object-key-paths';
const markdown = arrayToMarkdownTable(getPathEntries(report), {
columns: [
{ key: 'path', header: 'Path' },
{ key: 'depth', header: 'Depth' },
{ key: 'isLeaf', header: 'Leaf' }
]
});Use with json-csv-kit when discovered leaf paths should become a CSV export:
import { getLeafPaths } from 'object-key-paths';
import { jsonToCsv } from 'json-csv-kit';
const columns = getLeafPaths(report).map((path) => ({
key: path,
header: path
}));
const csv = jsonToCsv([report], { columns });- Only own enumerable string keys are traversed.
- Arrays are traversed by numeric index unless
includeArraysis disabled. Date,Map,Set, functions and class instances are treated as leaf values by default.- Circular references are not followed.
- This package discovers and formats paths; it does not parse paths or read values by path. Use
object-path-kitfor that.
MPL-2.0