Skip to content

fix: escape string values for date/date-time/time formats - #860

Merged
mcollina merged 1 commit into
mainfrom
escape-date-strings
Jul 29, 2026
Merged

fix: escape string values for date/date-time/time formats#860
mcollina merged 1 commit into
mainfrom
escape-date-strings

Conversation

@mcollina

Copy link
Copy Markdown
Member

Problem

asDateTime, asDate and asTime concatenate string inputs straight between quotes:

if (typeof date === 'string') {
  return '"' + date + '"'
}

So a string value containing ", \, a control character or a lone surrogate produces output that is not valid JSON:

const stringify = fastJson({
  type: 'object',
  properties: { ts: { type: 'string', format: 'date-time' } }
})

stringify({ ts: 'a"b' })   // {"ts":"a"b"}  -> JSON.parse throws

Plain type: string fields already go through asString, so whether a string got escaped depended on format, which is surprising.

Fix

  • Hoist the escaping routine to module scope as asString(str); the class method delegates to it. This is needed because the generated code destructures the serializer methods and calls them unbound, so this.asString isn't reachable from asDateTime & co.
  • Route the string branch of asDateTime / asDate / asTime through it.
  • Date inputs keep the plain concatenation — toISOString() output can never need escaping.
  • format: 'unsafe' is untouched; it remains the documented way to opt out of escaping.

Notes

  • Serializing a date-format string now costs the same as serializing a regular string of the same length (roughly 10ns -> 37ns per field in a tight loop for a 24-char ISO timestamp). Unavoidable if the output has to be valid JSON. Date values are unaffected.
  • Values are still not reformatted or validated as dates, only escaped. README note updated to say so.

Test

New case in test/date.test.js asserts all three formats round-trip quotes, backslashes, control characters and lone surrogates identically to JSON.stringify.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TmhA1oTx5qUWc6UmEgWuar

`asDateTime`, `asDate` and `asTime` concatenated string inputs straight
between quotes, so a value containing `"`, `\`, a control character or a
lone surrogate produced a document that is not valid JSON. Plain
`type: string` fields already went through `asString`, so the behavior
was inconsistent depending on `format`.

Route the string branch of the three functions through the existing
escaping routine, which is hoisted to module scope since the generated
code destructures the serializer methods and calls them unbound. `Date`
inputs keep the plain concatenation: `toISOString()` never needs
escaping.

Serializing a date-format string is now as expensive as serializing a
regular string of the same length; `Date` values are unaffected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TmhA1oTx5qUWc6UmEgWuar
@mcollina
mcollina requested a review from Fdawgs July 29, 2026 12:57
@mcollina

Copy link
Copy Markdown
Member Author

The real reason for this is so we stop receiving reports.

@mcollina
mcollina merged commit 93e54a4 into main Jul 29, 2026
17 checks passed
@mcollina
mcollina deleted the escape-date-strings branch July 29, 2026 13:43
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