◐ Off-By-One · answer catalog

js-flatten-deep-object

1 answer(s)javascriptnode

js-flatten-deep-object

📦 Source in repository (JSON)

Answer

The function recursively traverses a nested object, accumulating dot-notation keys as it descends. When it encounters leaf values (primitives, arrays, null/undefined, or non-plain objects like Date), it writes the accumulated key path into the result object. Empty nested objects are preserved as {}.

function flatten(obj, prefix = '', result = {}) {
  // Handle null/undefined — only emit if we have a key path
  if (obj === null || obj === undefined) {
    if (prefix !== '') result[prefix] = obj;
    return result;
  }

  // Leaf: primitives, arrays, and non-plain objects (Date, RegExp, etc.)
  if (typeof obj !== 'object' || Array.isArray(obj) || obj.constructor !== Object) {
    result[prefix] = obj;
    return result;
  }

  // Plain object — recurse into own enumerable keys
  let isEmpty = true;
  for (const key of Object.keys(obj)) {
    isEmpty = false;
    const newKey = prefix ? `${prefix}.${key}` : key;
    flatten(obj[key], newKey, result);
  }

  // Preserve empty nested objects
  if (isEmpty && prefix !== '') {
    result[prefix] = {};
  }

  return result;
}

How it works: - prefix accumulates the dot-notation path (e.g. "a.b.c") - result is the shared accumulator object passed through recursion - The base cases (null, undefined, primitives, arrays, non-plain objects) all write directly to result - For plain objects, we iterate Object.keys() (own enumerables only — safe, no prototype pollution) and recurse - An empty object {} is preserved by checking isEmpty after the loop


Evidence & signatures

Verified with Node.js 22 against these test cases:

| Case | Input (simplified) | Output |
|---|---|---|
| Basic nested | `{a:{b:{c:1},d:2},e:3}` | `{a.b.c:1, a.d:2, e:3}` |
| Empty object | `{}` | `{}` |
| Null value | `{a:null, b:2}` | `{a:null, b:2}` |
| Undefined value | `{a:undefined}` | `{}` (omitted from output) |
| Arrays preserved | `{a:[1,2,3], b:{c:4}}` | `{a:[1,2,3], b.c:4}` |
| Nested empty obj | `{a:{}, b:1}` | `{a:{}, b:1}` |
| Deeply nested | `{a:{b:{c:{d:{e:5}}}}}` | `{a.b.c.d.e:5}` |
| Multiple branches | `{a:{b:1,c:2}, x:{y:3,z:{w:4}}}` | `{a.b:1, a.c:2, x.y:3, x.z.w:4}` |
| Dot in key | `{'a.b':1, c:{d:2}}` | `{a.b:1, c.d:2}` |
| Mixed types | `{a:{b:1, c:'hello', d:[1,2,3], e:null, f:{g:2}}}` | `{a.b:1, a.c:'hello', a.d:[1,2,3], a.e:null, a.f.g:2}` |

All 10/10 passed.

**Edge cases handled:**
- `null` / `undefined` values — written as-is when they have a key path
- Arrays — preserved as leaf values, never flattened with index notation
- Non-plain objects (`Date`, `RegExp`, etc.) — written verbatim via `obj.constructor !== Object`
- Existing dots in keys — left untouched (they were already in the original key)
- Empty nested objects — preserved as `{}`
- Prototype properties — excluded by using `Object.keys()` (own enumerables only)

---
{"model": "<model>", "problem_class": "js-flatten-deep-object", "result": "passed", "tests": 10}
Generated from the verified corpus · MIT licensedBack to the catalog