From 18ab55d9abd80d10eb908f7339720a1f3cbe0af7 Mon Sep 17 00:00:00 2001 From: Yarchik Date: Thu, 23 Jul 2026 14:34:07 +0100 Subject: [PATCH] Preserve arrays under unflatten when safe and object are both set `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`. --- index.js | 5 ++++- test/test.js | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 42e1ddc..9c32520 100644 --- a/index.js +++ b/index.js @@ -99,7 +99,10 @@ export function unflatten (target, opts) { target = Object.keys(target).reduce(function (result, key) { const type = Object.prototype.toString.call(target[key]) const isObject = (type === '[object Object]' || type === '[object Array]') - if (!isObject || isEmpty(target[key])) { + // a `safe`-preserved array is a leaf value here, not a nested object to + // re-flatten; pass it through so `object: true` doesn't rebuild it as an + // object and break the `safe` contract of keeping arrays intact + if (!isObject || isEmpty(target[key]) || (opts.safe && Array.isArray(target[key]))) { result[key] = target[key] return result } else { diff --git a/test/test.js b/test/test.js index ef01c9b..954c2f2 100644 --- a/test/test.js +++ b/test/test.js @@ -425,6 +425,12 @@ describe('Unflatten', function () { bar: {} }), { foo: [], bar: {} }) }) + + test('Should keep arrays when both safe and object are set', function () { + const opts = { safe: true, object: true } + const flat = flatten({ tags: ['red', 'green', 'blue'] }, opts) + assert.deepStrictEqual(unflatten(flat, opts), { tags: ['red', 'green', 'blue'] }) + }) }) describe('.object', function () {