fix: preserve invalid Date instead of dropping it to null#355
Open
greymoth-jp wants to merge 1 commit into
Open
fix: preserve invalid Date instead of dropping it to null#355greymoth-jp wants to merge 1 commit into
greymoth-jp wants to merge 1 commit into
Conversation
An invalid Date (e.g. new Date(NaN), or new Date('') from a failed parse)
was not matched by the Date transformer because isDate excludes dates with
a NaN time. It therefore fell through to plain JSON, where it became null,
losing both its value and its Date type on parse.
Match any Date instance in the Date rule and serialize an invalid Date to
an empty string, which new Date('') revives back into an invalid Date. Valid
dates are unchanged. isDate stays strict, so its existing semantics and test
are untouched.
Skn0tt
reviewed
Jun 29, 2026
Skn0tt
left a comment
Collaborator
There was a problem hiding this comment.
I like the idea! Left a note about the string representation. Let's make sure that this is backwards compatible, can you add a test case about that?
| date: new Date('invalid'), | ||
| }, | ||
| output: { | ||
| date: '', |
Collaborator
There was a problem hiding this comment.
let's use Invalid Date as the sentinel value instead of ''.
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.
Problem
isDateonly matches dates with a valid time (payload instanceof Date && !isNaN(payload.valueOf())), so an invalid Date never reaches the Date transformer. It falls through to plain JSON, whereJSON.stringify(new Date(NaN))producesnull. Afterparse, the value comes back asnull, losing both the value and theDatetype:This is a silent type change. Invalid Dates turn up in real code whenever a date is built from untrusted input, e.g.
new Date(userInput)where the input does not parse. The caller then receivesnullwhere it expected aDate, which breaks any later.getTime()/.toISOString()call.Fix
Match any
Dateinstance in the Date rule and move the "no ISO representation" concern into the serializer: an invalid Date is serialized to an empty string, andnew Date('')revives it back into an invalid Date. This is the same approachdevalueuses (it stores an invalid Date as[["Date",""]]and reconstructs it withnew Date('')).isDateis left strict, so its existing meaning ("a valid Date") and its test stay untouched. Valid dates serialize exactly as before.Verification
Checked both ways, with the existing suite plus a new case:
{ json: "", meta: { values: ["Date"] } }and parses back to an invalidDate(instanceof Date, NaN time)null: staysnullnpm teststays green. The added "works for invalid dates" case fails onmainand passes with this change.Scope
Only the invalid-Date path changes. Output produced by older versions (where an invalid Date became
nullwith no annotation) still deserializes tonull, so there is no change for previously serialized data.Greptile Summary
This PR fixes a silent type-erasure bug where invalid
Dateobjects (e.g.new Date(NaN)) were serialized tonulland lost theirDateannotation on round-trip. The fix widens theisApplicableguard from the strictisDatehelper tov instanceof Dateand serializes invalid dates as an empty string"", whichnew Date("")faithfully revives to an invalid Date.src/transformer.ts: replaces theisDateimport with an inlinev instanceof Datepredicate and adds a ternary in the serializer that emits""for invalid dates instead of calling.toISOString().src/index.test.ts: adds a"works for invalid dates"case that verifies the serialized form ({ date: "" }), the Date annotation, and that the parsed value isinstanceof Datewith a NaN time.Confidence Score: 5/5
Safe to merge — the only changed path is the invalid-Date serialization, and valid dates, nulls, and all other types are completely unaffected.
The fix is narrowly scoped: it adds one ternary in the Date serializer and widens the isApplicable guard from valid-only to any Date instance. Valid dates continue to emit their ISO string exactly as before. The empty-string sentinel for invalid dates is a well-established pattern (used by devalue), new Date('') is universally an Invalid Date, and backward compatibility with previously serialized data is preserved. Tests cover the new case, the existing valid-date case is unchanged, and no other logic in the file is touched.
No files require special attention.
Important Files Changed
isDateguard withv instanceof Dateand serializes invalid dates to""instead of falling through to JSON null; logic is correct and backward-compatible."works for invalid dates"test case with correct assertions for the serialized form, the Date annotation, and the revived invalid Date.Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A["Value to serialize"] --> B{"v instanceof Date?"} B -- No --> C["Check other rules / fall through to JSON"] B -- Yes --> D{"isNaN(v.valueOf())?"} D -- Yes (invalid Date) --> E["Serialize to ''\n+ 'Date' annotation"] D -- No (valid Date) --> F["Serialize to v.toISOString()\n+ 'Date' annotation"] E --> G["JSON: { json: '', meta: { values: ['Date'] } }"] F --> H["JSON: { json: '2024-01-01T...', meta: { values: ['Date'] } }"] G --> I["Deserialize: new Date('')"] H --> J["Deserialize: new Date(isoString)"] I --> K["Invalid Date (NaN time)"] J --> L["Valid Date"]%%{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"}}}%% flowchart TD A["Value to serialize"] --> B{"v instanceof Date?"} B -- No --> C["Check other rules / fall through to JSON"] B -- Yes --> D{"isNaN(v.valueOf())?"} D -- Yes (invalid Date) --> E["Serialize to ''\n+ 'Date' annotation"] D -- No (valid Date) --> F["Serialize to v.toISOString()\n+ 'Date' annotation"] E --> G["JSON: { json: '', meta: { values: ['Date'] } }"] F --> H["JSON: { json: '2024-01-01T...', meta: { values: ['Date'] } }"] G --> I["Deserialize: new Date('')"] H --> J["Deserialize: new Date(isoString)"] I --> K["Invalid Date (NaN time)"] J --> L["Valid Date"]Reviews (1): Last reviewed commit: "fix: preserve invalid Date instead of dr..." | Re-trigger Greptile