Unsquash ObjectLoading saved progress…

Unsquash Object

Implement unsquashObject(flat) — the inverse of squash-object. You're given a single-level object whose keys are dot-delimited paths, and you rebuild the nested object they describe. unsquashObject({ 'a.b': 1 }) becomes { a: { b: 1 } }. Think of it as turning a flat list of full file paths back into a folder tree: docs/notes/todo.txt becomes a docs folder containing a notes folder containing todo.txt. This is the expansion that config loaders and form libraries do to turn a flat user.address.city key back into a real nested shape. Keep it symmetric with squashObject so a round-trip is lossless.

Signature

// flat: a single-level object. Every key is a dot-joined path string;
//       every value is a LEAF — a primitive, null, an array, or an object,
//       kept exactly as-is (never re-split or recursed into).
// returns: a new nested object whose structure is described by the dotted keys.
function unsquashObject(flat): Record<string, unknown>;

Examples

// One dotted key becomes one level of nesting.
unsquashObject({ 'a.b': 1 });
// → { a: { b: 1 } }
// A deep path, a shorter path that shares its prefix, and a flat key.
unsquashObject({ 'a.b.c': 1, 'a.d': 2, e: 3 });
// → { a: { b: { c: 1 }, d: 2 }, e: 3 }
// Leaves are stored as-is — an array is never treated as a branch.
unsquashObject({ 'tags': ['x', 'y'], 'meta.author': null });
// → { tags: ['x', 'y'], meta: { author: null } }

Notes

  • Split each key on . and walk the path. For every entry, break the key into segments. Every segment except the last names an intermediate object to step into (creating it if it isn't there yet); the last segment names where the value lands.
  • A key with no dot stays top-level. { e: 3 } has a one-segment path, so it becomes { e: 3 } — no nesting.
  • Keys that share a prefix merge. { 'a.b': 1, 'a.c': 2 } builds a single a object holding both b and c. The second key must reuse the a you already created, not clobber it.
  • Values are leaves — stored as-is. A value that is an array, null, an object, or a primitive is set verbatim at the final segment. Don't recurse into it; don't re-split its own keys on dots. This mirrors squashObject's leaf rule so the two round-trip cleanly.
  • Conflicts: the later key wins. If a prefix is used as both a leaf and a branch — { a: 1, 'a.b': 2 } — the key that comes later decides. Here 'a.b' overwrites the leaf 1, giving { a: { b: 2 } }. Reverse the order and the later leaf wins instead.
  • Don't mutate the input. Read flat; build and return a fresh nested object.
Loading editor…