Repository navigation
Conversation
|
No review on this since June. Here it is measured, and the measurement turned up something the PR title does not cover, so I would rather put both in front of you. On A plain number gets a dedicated transformer: // src/transformer.ts:158
simpleTransformation<number, '-0', 'number'>(
(v): v is number => v === 0 && 1 / v === -Infinity,
'number',
() => '-0',
Number
),The typed-array rule, ninety lines further down, handles the other three specials and not this one: // src/transformer.ts:248
if (typeof n === 'number') {
if (Number.isNaN(n)) return 'NaN';
if (n === Infinity) return 'Infinity';
if (n === -Infinity) return '-Infinity';
}
return n;The predicate this PR adds is the same expression already used at line 159, so the change is the existing decision applied to the sibling path rather than a new idea. On the published release, the gap is wider than that. Running The type annotation is preserved and a I checked my own control before believing any of the above. My first run reported that The test I added asserts with One thing I am not claiming: $ gh pr checks 356 --repo ravionhq/superjson
no checks reported on the 'fix-typed-array-negative-zero' branchNothing runs on this branch, so the tables above are mine, produced locally against the published package rather than by your CI. Happy to rebase, or to take the three-line change directly and close this. No attribution needed. |
stringify/parsedrops the sign of negative zero when it is stored in aFloat32ArrayorFloat64Array:The typed-array transformer already encodes the special float values that a JSON round-trip cannot represent:
NaN,Infinityand-Infinityare written out as strings (#292, #349).-0is the one that was left out.JSON.stringify(-0)returns"0", so the sign is gone as soon as the serialized payload passes through JSON.superjson preserves
-0everywhere else (the dedicated number rule from regression #83), and the object-onlyserialize/deserializepath kept it too because it passes the raw value through untouched. Only the string path lost it, and only for the two float-backed typed arrays, since integer and clamped arrays cannot hold-0.This stores
-0as"-0"and reads it back, the same wayNaN/Infinityare handled in that function. I added a regression test next to the existing #292 case.Greptile Summary
This PR fixes a bug where
-0stored inFloat32ArrayorFloat64Arraywas silently dropped during JSON serialization, coming back as+0after astringify/parseround-trip. It extends the existing special-float-value encoding intypedArrayRuleto treat-0the same wayNaN,Infinity, and-Infinityare already handled.src/transformer.ts: Adds a-0detection check (n === 0 && 1 / n === -Infinity) in the serializer and a corresponding'-0'→-0conversion in the deserializer, following the exact pattern already used for the other IEEE 754 special values.src/index.test.ts: Adds a regression test that verifies round-trip fidelity for-0entries in bothFloat64ArrayandFloat32Array, and also confirms positive0is not incorrectly converted.Confidence Score: 5/5
Safe to merge — the change is minimal, isolated to the typed-array transformer, and follows the existing pattern exactly.
Both the serializer and deserializer changes are a single-line addition each, directly mirroring the already-proven NaN/Infinity handling. The negative-zero detection idiom (n === 0 && 1 / n === -Infinity) is correct and widely used in JS. The new test covers mixed arrays with both -0 and +0 to prevent false positives, and integer typed arrays are unaffected since they can never hold -0 in the first place.
No files require special attention.
Important Files Changed
Sequence Diagram
%%{init: {'theme': 'neutral'}}%% sequenceDiagram participant App participant SuperJSON participant typedArrayRule participant JSON App->>SuperJSON: stringify(new Float64Array([-0, 0, 1])) SuperJSON->>typedArrayRule: serialize(Float64Array) typedArrayRule->>typedArrayRule: map each element Note over typedArrayRule: -0 → '-0' (new)<br/>0 → 0<br/>1 → 1 typedArrayRule-->>SuperJSON: ['-0', 0, 1] + annotation SuperJSON->>JSON: JSON.stringify(['-0', 0, 1]) JSON-->>App: "'{"json":{"a":["-0",0,1]},...}'" App->>SuperJSON: "parse('{"json":{"a":["-0",0,1]},...}')" SuperJSON->>JSON: JSON.parse(...) JSON-->>SuperJSON: "{a: ['-0', 0, 1]}" SuperJSON->>typedArrayRule: deserialize(['-0', 0, 1], 'Float64Array') typedArrayRule->>typedArrayRule: map each element Note over typedArrayRule: '-0' → -0 (new)<br/>0 → 0<br/>1 → 1 typedArrayRule-->>SuperJSON: new Float64Array([-0, 0, 1]) SuperJSON-->>App: Float64Array [-0, 0, 1]%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%% sequenceDiagram participant App participant SuperJSON participant typedArrayRule participant JSON App->>SuperJSON: stringify(new Float64Array([-0, 0, 1])) SuperJSON->>typedArrayRule: serialize(Float64Array) typedArrayRule->>typedArrayRule: map each element Note over typedArrayRule: -0 → '-0' (new)<br/>0 → 0<br/>1 → 1 typedArrayRule-->>SuperJSON: ['-0', 0, 1] + annotation SuperJSON->>JSON: JSON.stringify(['-0', 0, 1]) JSON-->>App: "'{"json":{"a":["-0",0,1]},...}'" App->>SuperJSON: "parse('{"json":{"a":["-0",0,1]},...}')" SuperJSON->>JSON: JSON.parse(...) JSON-->>SuperJSON: "{a: ['-0', 0, 1]}" SuperJSON->>typedArrayRule: deserialize(['-0', 0, 1], 'Float64Array') typedArrayRule->>typedArrayRule: map each element Note over typedArrayRule: '-0' → -0 (new)<br/>0 → 0<br/>1 → 1 typedArrayRule-->>SuperJSON: new Float64Array([-0, 0, 1]) SuperJSON-->>App: Float64Array [-0, 0, 1]Reviews (1): Last reviewed commit: "fix: preserve negative zero in Float32Ar..." | Re-trigger Greptile