Skip to content

Preserve arrays under unflatten when safe and object are both set - #194

Open
spokodev wants to merge 1 commit into
hughsk:masterfrom
spokodev:fix/safe-object-array-preservation
Open

Preserve arrays under unflatten when safe and object are both set#194
spokodev wants to merge 1 commit into
hughsk:masterfrom
spokodev:fix/safe-object-array-preservation

Conversation

@spokodev

Copy link
Copy Markdown

What

The README says safe makes "both flatten and unflatten preserve arrays and their contents". But when object: true is also set, unflatten turns a preserved array back into a plain object:

const opts = { safe: true, object: true }
const flat = flatten({ tags: ['red', 'green', 'blue'] }, opts) // { tags: ['red','green','blue'] } (array kept)
unflatten(flat, opts)
// actual:   { tags: { '0': 'red', '1': 'green', '2': 'blue' } }   <- no longer an array
// expected: { tags: ['red', 'green', 'blue'] }

safe and object are orthogonal (safe = keep existing arrays; object = don't turn numeric-keyed objects into arrays) and combining them is legitimate, but object overrides safe's array preservation. safe alone round-trips correctly.

Root cause

unflatten never truly preserves arrays — it only rebuilds them as a side effect of the numeric-key → array branch (typeof key2 === 'number' && !opts.object). Its "messy objects" reduce re-flattens every container value, including a safe-kept array, back into the flat keyspace; with object: true the rebuild produces a plain object instead of an array. safe is never consulted in unflatten to keep an already-array value intact.

Fix

In the reduce, pass a safe array through as a leaf instead of re-flattening it, so it survives the reconstruction unchanged regardless of object.

Tests

Added a .safe case asserting { safe: true, object: true } round-trips { tags: ['red','green','blue'] } as an array. It fails on master and passes with the fix; the suite and standard are green.

`safe` is documented to make "both flatten and unflatten preserve arrays
and their contents", but with `object: true` also set, `unflatten` turns a
preserved array back into a plain object:

  const opts = { safe: true, object: true }
  unflatten(flatten({ tags: ['a', 'b', 'c'] }, opts), opts)
  // { tags: { '0': 'a', '1': 'b', '2': 'c' } }  -- no longer an array

`unflatten` never truly preserves arrays; it only rebuilds them as a side
effect of the numeric-key -> array logic, which `object: true` disables. Its
"messy objects" reduce re-flattens every container value, including a
`safe`-kept array, back into the flat keyspace, and it is then rebuilt as an
object. `safe` alone round-trips correctly because the array is rebuilt
numerically.

Pass a `safe` array through the reduce instead of re-flattening it, so it
survives unchanged regardless of `object`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant