Repository navigation
fix(typed-array): serialize non-finite floating-point values - #73
Merged
Merged
Conversation
A formatter closure adds finite checks to integer arrays and an extra call for floating-point values. Inline checks retain direct conversion for integer and BigInt arrays. On Node 24.20.0 (V8 13.6.233.17-node.53), CPU time per call falls from 4.92 to 4.11 µs for 128 indented Int32Array values. For 128 compact Float64Array values, it falls from 8.27 to 7.54 µs. These means use two fresh processes with 50k warmup calls and seven trials per shape, excluding the extremes. Refs: #69
BridgeAR
marked this pull request as ready for review
October 13, 2026 04:21
kitschpatrol
pushed a commit
to kitschpatrol/safe-stable-stringify
that referenced
this pull request
Nov 2, 2026
* fix: do not throw on a negative number `space` (BridgeAR#65) `JSON.stringify` treats any numeric `space` below 1 as no indentation, but `stringify` threw on it: JSON.stringify({ a: 1 }, null, -1) // '{"a":1}' stringify({ a: 1 }, null, -1) // RangeError: Invalid count value: -1 The README documents the spacer as behaving "the same as with JSON.stringify()". `space` was clamped to a maximum of 10 but not a minimum of 0, so a negative count reached `String.prototype.repeat`, which throws for counts <= -1. Clamp the lower bound to 0. * Fix merge conflict in readme (BridgeAR#63) Looks like this was accidentally introduced in BridgeAR@132e76c * fix: enforce maximumDepth for nested objects with array replacers (BridgeAR#64) * feat(circular)!: include ancestor depth in default markers (BridgeAR#70) Circular references to different ancestors currently produce the same marker, which hides where each cycle closes. The new marker identifies the referenced ancestor's depth, starting at 0 for the root. BREAKING CHANGE: The default marker changes from `[Circular]` to `[Circular: N]`. Set `circularValue: '[Circular]'` to retain the previous output. * fix(typed-array): serialize non-finite floating-point values (BridgeAR#73) * Serialize non-finite typed-array values consistently Fixes: BridgeAR#68 * perf(typed-array): inline floating-point checks A formatter closure adds finite checks to integer arrays and an extra call for floating-point values. Inline checks retain direct conversion for integer and BigInt arrays. --------- Co-authored-by: Ferit <frtcm11@gmail.com> * docs(circular): clarify JSON escaping for custom markers (BridgeAR#74) * perf(arrays): defer index string conversion (BridgeAR#75) Array indexes only need strings when they reach toJSON or function replacer callbacks. A four-number array uses 86 ns instead of 107 ns on Node 26.3.1 (V8 14.6.202.34-node.20), a 20% CPU reduction. Measurements use 50,000 warmup calls and seven alternating trials, with the fastest and slowest removed. Drive-by fix: Mark intentional invalid-option assertions for TypeScript. * perf(sort): bound insertion sort work (BridgeAR#76) Disordered objects with up to 200 keys can spend most of their serialization time shifting keys. A shuffled 200-key object uses 16.6 us instead of 45.6 us on Node 26.3.1 (V8 14.6.202.34-node.20), a 64% CPU reduction. Measurements use 50,000 warmup calls and seven alternating trials, with the fastest and slowest removed. Nearly ordered keys retain the insertion path. Drive-by fix: Mark intentional invalid-option assertions for TypeScript. * feat(modules)!: share ESM and CommonJS exports (BridgeAR#72) The implementation is cached after the first load, so removing its global export leaves the other wrapper without an export. Retain a version-specific symbol so ESM imports, CommonJS requires, and independent copies of one version receive the same function. BREAKING CHANGE: Require Node.js 18 or later. * perf: reduce primitive serialization overhead (BridgeAR#77) Primitive array elements and object properties enter recursive serializers even though they need no traversal. Share primitive formatting across serialization modes while preserving toJSON ordering and per-value error handling in safe mode. Native string escaping becomes faster at shorter string lengths on Node.js 22 and later. Select the cutoff once at module load so older runtimes retain their existing fast path. --------- Co-authored-by: Spoko Dev <spoko.dev@gmail.com> Co-authored-by: Andy Trevorah <andy@trevorah.net> Co-authored-by: Chamath Adithya <achamath1@gmail.com> Co-authored-by: Ruben Bridgewater <ruben@bridgewater.de> Co-authored-by: Ferit <frtcm11@gmail.com> Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Floating typed arrays bypass number normalization in the optimized paths, so NaN and infinities produce invalid JSON and escape strict-mode rejection.
The maintainer changes restrict finite checks to floating-point arrays and run them inline, preserving direct conversion for integer and BigInt arrays.
On Node 24.20.0 (V8 13.6.233.17-node.53), CPU time per call falls from 4.92 to 4.11 µs against the contributor commit for 128 indented Int32Array values. For 128 compact Float64Array values, it falls from 8.27 to 7.54 µs. These means use two fresh processes with 50k warmup calls and seven trials per shape, excluding the extremes.
Closes: #69
Fixes: #68