← Daily Logs
Devtools Tech

Create a Flat Version of a Nested Object

MediumJul 3, 2026objectsrecursionbreadcrumbs

Turn a deeply nested object into a single-level map of underscore-joined keys — useful for breadcrumb trails or flattened form state. The one rule that keeps it from being a plain recursive walk: arrays are values, not containers to descend into.

The problem

Implement `transform(obj, prefix)` that flattens a nested object into a single level, joining each chain of keys with `_` and prefixing every resulting key with `prefix`. Given `{ channel: { youtube: { link: '...' } } }` and prefix `'data'`, the flattened key is `data_channel_youtube_link`.

Arrays must be preserved as-is — `{ resources: { pages: ['/a', '/b'] } }` keeps `pages` as an array value under its flattened key, rather than flattening `0` and `1` as further nested keys. Only plain objects get walked into; everything else (arrays, strings, numbers, booleans) is a leaf.

The approach

A single recursive helper `flatten(current, keyPath)` carries the key path built so far. At each call, check whether `current` is a plain object — `typeof current === 'object' && current !== null && !Array.isArray(current)` — and if so, recurse into every key with `keyPath` extended by `_key`; if not, `current` is a leaf (primitive or array) and gets written straight to `result[keyPath]`.

That single `Array.isArray` check is what keeps arrays intact: an array satisfies `typeof … === 'object'` but fails the array guard, so it falls into the else branch and is assigned as a value rather than walked key-by-key.

The prefix is just the seed value passed into the very first call — `flatten(collection, prefix)` — so it rides along through every recursive call automatically instead of needing to be prepended separately at the end.

Building the result in a closed-over `result` object, rather than merging return values at each level, avoids repeated spreads/merges through a deep recursion, and keeps `collection` itself untouched since nothing is ever written back into it.

The solution

js
function transform(collection, prefix) {
    const result = {};

    function flatten(current, keyPath) {
        if (typeof current === 'object' && current !== null && !Array.isArray(current)) {
            for (const key in current) {
                flatten(current[key], `${keyPath}_${key}`);
            }
        } else {
            result[keyPath] = current;
        }
    }

    flatten(collection, prefix);
    return result;
};

Time O(n)Space O(n)

All entries