From 0158c4194606b509b2e474d1c3148e655d8799e8 Mon Sep 17 00:00:00 2001 From: Ferit Date: Sun, 11 Oct 2026 18:00:46 +0300 Subject: [PATCH] fix: escape custom reducer names --- .changeset/quiet-reducer-names.md | 5 +++++ src/stringify.js | 2 +- test/index.test.js | 16 ++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 .changeset/quiet-reducer-names.md diff --git a/.changeset/quiet-reducer-names.md b/.changeset/quiet-reducer-names.md new file mode 100644 index 0000000..d8338a3 --- /dev/null +++ b/.changeset/quiet-reducer-names.md @@ -0,0 +1,5 @@ +--- +'devalue': patch +--- + +Escape custom reducer names when serializing them. diff --git a/src/stringify.js b/src/stringify.js index 9d3a6b5..7c2a930 100644 --- a/src/stringify.js +++ b/src/stringify.js @@ -140,7 +140,7 @@ function run(async, value, reducers, options) { for (const { key, fn } of custom) { const value = fn(thing); if (value) { - stringified[index] = `["${key}",${flatten(value)}]`; + stringified[index] = `[${stringify_string(key)},${flatten(value)}]`; return index; } } diff --git a/test/index.test.js b/test/index.test.js index c982610..6eeaf41 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -1715,6 +1715,22 @@ describe.each(Object.entries(fixtures))('stringify: %s', (_name, tests) => { }); describe('parse wrapper', () => { + test.each(['quote"', 'backslash\\', 'line\nfeed', ''])( + 'round-trips custom reducer name %j', + async (name) => { + const value = new Foo(42); + const reducers = { [name]: (value) => value instanceof Foo && value.value }; + const revivers = { [name]: (value) => new Foo(value) }; + for (const serialized of [ + stringify(value, reducers), + await stringifyAsync(value, reducers) + ]) { + expect(parse(serialized, revivers)).toEqual(value); + expect(serialized.includes('')).toBe(false); + } + } + ); + test('parses normal input', () => { expect(parse('[{"answer":1},42]')).toEqual({ answer: 42 }); });