Skip to content

fix: serialize an Invalid Date instead of throwing#24

Merged
WebReflection merged 1 commit into
ungap:mainfrom
spokodev:fix-invalid-date-serialize
Jun 23, 2026
Merged

fix: serialize an Invalid Date instead of throwing#24
WebReflection merged 1 commit into
ungap:mainfrom
spokodev:fix-invalid-date-serialize

Conversation

@spokodev

Copy link
Copy Markdown
Contributor

Problem

serialize (and the /json stringify) throws on an Invalid Date, even though native structuredClone — the API this package ponyfills — clones it without issue:

import {serialize, deserialize} from '@ungap/structured-clone';

structuredClone(new Date(NaN));        // Invalid Date, preserved
deserialize(serialize(new Date(NaN))); // RangeError: Invalid time value

An Invalid Date is a legitimate value (e.g. the result of new Date('not a date')), so any object graph that happens to contain one crashes the clone. The README documents no Invalid-Date caveat (the only stated limitation is unsupported array elements becoming null).

Cause

Date.prototype.toISOString() throws a RangeError when the date's time value is NaN, and the DATE case calls it unconditionally:

case DATE:
  return as([TYPE, value.toISOString()], value);

The deserialize side already handles it: it does new Date(value), which faithfully revives an Invalid Date.

Fix

Store an empty string for an Invalid Date:

case DATE:
  return as([TYPE, isNaN(value.getTime()) ? EMPTY : value.toISOString()], value);

EMPTY ('') is chosen deliberately over NaN/getTime(): it is JSON-safe, so the /json path round-trips too (NaN would serialize to null, and new Date(null) revives to epoch 0), and new Date('') revives to an Invalid Date — matching the deserialize side. Valid dates are unaffected.

Tests

Added round-trip assertions for an Invalid Date on both the record API and the /json API. They throw on main and pass with the fix; the rest of the suite is unchanged.

(The minified structured-json.js browser bundle is a build artifact regenerated by npm run build, so it is left out of this diff.)

`Date.prototype.toISOString()` throws a RangeError on an Invalid Date
(`new Date(NaN)`), so `serialize` (and the `/json` `stringify`) crashed
on a value that native `structuredClone` clones without issue:

    structuredClone(new Date(NaN));   // Invalid Date, preserved
    deserialize(serialize(new Date(NaN)));  // RangeError: Invalid time value

Store an empty string for an Invalid Date. It is JSON-safe (so the `/json`
path round-trips too) and `new Date('')` revives back to an Invalid Date,
matching the deserialize side, which already does `new Date(value)`.
@WebReflection WebReflection merged commit 4e8eeaf into ungap:main Jun 23, 2026
1 check passed
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.

2 participants