Skip to content

fix: pass array indices to the replacer as strings - #32

Merged
Kikobeats merged 1 commit into
masterfrom
Kikobeats/replacer-array-keys
Oct 13, 2026
Merged

Kikobeats merged 1 commit into
masterfrom
Kikobeats/replacer-array-keys

Conversation

@Kikobeats

@Kikobeats Kikobeats commented Oct 13, 2026 •

Copy link
Copy Markdown
Owner

Closes #31

What changed

opts.replacer received array indices as numbers (0, 1). Native JSON.stringify passes them as strings ('0', '1'), so a replacer written for native JSON, like key === '0', silently skipped array items.

lib/index.js now passes '' + i for array elements.

const replacer = (key, value) => (key === '0' ? 'updated' : value)

JSON.stringify(['original'], replacer) // ["updated"]
stringify(['original'], { replacer }) // before: ["original"], now: ["updated"]

Behavior change to call out in the release note

A replacer that relied on the old numeric keys (key === 0, typeof key === 'number') stops matching array items. That is the native behavior, and lib/index.d.ts already types key: string.

How it was tested

  • npm test: lint + 45 passing, 100% coverage.
  • New in test/replacer.js:
    • the issue's repro, asserted against native output
    • the replacer's (typeof key, key) call sequence compared with native JSON.stringify over 8 array fixtures (flat, nested, inside objects, sparse, undefined/null, 12 items, empty), under default options and under space + cycles
    • an array returned by the replacer gets string indices
    • this is the holding array
  • The new tests fail on master and pass with the fix.
  • Independent adversarial review: 120 input/option combinations compared with native, no array key differs; 204 combinations compared between master and this branch, zero output differences.

Performance

Array-heavy input, no replacer, median of 9 runs x 200 iterations, 3 interleaved rounds:

variant median ms
master 277 to 279
String(i) 291 to 297
'' + i (this PR) 281 to 285

'' + i was chosen for that reason: roughly 1% over master against roughly 5% for String(i).

Notes

  • The pre-commit hook (prettier-standard) reformatted a few untouched lines in lib/index.js and test/replacer.js. The only logic change is the '' + i line.
  • Out of scope, already present on master and found during review: toJSON is called without the key argument; an array passed as opts.replacer throws; boxed primitives (Object(1)) serialize as {}; a replacer that pushes to its holding array changes the output because length is re-read each iteration.

🤖 Generated with Claude Code


Note

Medium Risk
Behavior change for any replacer that matched array items via numeric keys; otherwise localized to replacer/array serialization with strong native-parity tests.

Overview
Fixes replacer callbacks so array element keys are strings ('0', '1', …), matching native JSON.stringify instead of numeric indices.

In lib/index.js, recursive serialization now passes '' + i when walking arrays, so replacers that check key === '0' (or similar) apply to array items as they do with the built-in API. Replacers that depended on numeric keys (key === 0) will no longer match array elements—this is intentional alignment with documented replacer?: (key: string, …) typing.

test/replacer.js adds coverage for the reported case, parity of replacer key sequences vs JSON.stringify across several array shapes and option sets, this binding on arrays, and string indices when the replacer returns a nested array. Remaining diff in those files is mostly formatting.

Reviewed by Cursor Bugbot for commit 0c6f1c8. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • Bug Fixes
    • Replacer functions now receive string index keys for array elements, matching JSON.stringify behavior.

Native JSON.stringify calls the replacer with string keys for array
elements. Passing the numeric loop index made a replacer written for
native JSON (`key === '0'`) silently skip array items.

`'' + i` is used over `String(i)`: on an array-heavy benchmark with no
replacer it measured ~1% over baseline against ~5% for `String(i)`.

Closes #31

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 385a76a5-7047-437a-a379-15ef14f6e229
📥 Commits

Reviewing files that changed from the base of the PR and between 680448e and 0c6f1c8.

📒 Files selected for processing (2)
  • lib/index.js
  • test/replacer.js

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

📝 Walkthrough

Array element indices are now passed to replacers as strings. New tests compare replacer keys and results with JSON.stringify across array and object inputs, including configured options.

Changes: Array Replacer Key Compatibility

Layer / File(s) Summary
String array keys and replacer tests
lib/index.js, test/replacer.js
Array indices are converted to strings before they reach the replacer. Tests cover string-index replacements, replacer context, array-returning replacers, and parity with JSON.stringify.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 0c6f1

The array-key compatibility change is ready to merge after normal checks.

Pre-merge checks · Passed 4 · Failed 1

Failed Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks (4 passed)
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: passing array indices to the replacer as strings.
Linked Issues check Passed Issue #31 requires array-element replacer keys to match native JSON.stringify, including the string key '0'. In lib/index.js, array recursion now calls _deterministic(node, '' + i, node[i], level + 1)…
Out of Scope Changes check Passed The reviewed changes are limited to the array-key conversion in lib/index.js and related replacer tests and formatting in test/replacer.js. The tests directly verify issue #31 behavior and native pari…
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coveralls

Copy link
Copy Markdown

Coverage Status

Coverage is 100.0% — Kikobeats/replacer-array-keys into master. No base build found for master.

@Kikobeats
Kikobeats merged commit 7557891 into master Oct 13, 2026
4 checks passed
@Kikobeats
Kikobeats deleted the Kikobeats/replacer-array-keys branch October 13, 2026 07:13
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.

The replacer receives numeric keys for array elements instead of strings

2 participants