⚡ Sangsakala Proxy 🏠 Home
Skip to content

fix: preserve Unicode RawJSON values with ensure_ascii=True - #258

Merged
lelit merged 1 commit into
python-rapidjson:masterfrom
parijat1222q:fix/rawjson-unicode-ascii
Nov 3, 2026
Merged

lelit merged 1 commit into
python-rapidjson:masterfrom
parijat1222q:fix/rawjson-unicode-ascii

Conversation

@parijat1222q

Copy link
Copy Markdown
Contributor

Preserve Unicode RawJSON values when ensure_ascii=True.

RawValue transcodes Unicode code points to an ASCII target without JSON escaping. Pass the ensure_ascii setting through the recursive encoder and escape non-ASCII RawJSON characters before that call, including UTF-16 surrogate pairs for astral characters. Existing ASCII raw spelling/whitespace and ensure_ascii=False literal behavior remain unchanged. Normal string encoding is unchanged. The shared stream-error check is retained.

Docs clarify that ensure_ascii=True escapes non-ASCII characters in RawJSON. This follows the encoder's ASCII-output guarantee rather than preserving corrupt output under a literal interpretation of 'verbatim'. RawJSON still accepts unvalidated content; this is not a general JSON validator or a fix for embedded-NUL input.

Validation on Linux CPython 3.14.8 with the native extension rebuilt from each source state:

  • 24 parameterized regression cases fail on unchanged master; 30 RawJSON/control cases pass.
  • Full patched tests: 1448 passed, 31 skipped, 2 xfailed.
  • 277 doctests passed, with one existing missing_static directory warning.
  • Installed-wheel stubtest passed outside the checkout.
  • 160 additional finite API/format/flag combinations passed.

Tests cover dumps/Encoder/text+binary dump, compact/pretty output, ASCII flag on/off, BMP/astral Unicode, unchanged raw whitespace, and stream exceptions. No speedup, hosted CI, sanitizer, debug-Python or cross-platform result is claimed.

Related but distinct: #169 discusses RawJSON performance. No competing corruption fix found in the current open PRs, targeted all-state searches, or relevant recent commit history.

Compatibility decision: the existing RawJSON docs promise verbatim insertion including whitespace, while dumps docs say all incoming non-ASCII characters are escaped under ensure_ascii=True. Current behavior satisfies neither for these inputs: it truncates Unicode or raises rather than inserting the original value. The proposal gives the global ASCII setting precedence for valid RawJSON string content. That deliberately changes literal non-ASCII spelling to escapes under the default; callers who require exact Unicode text must use ensure_ascii=False. An alternative would preserve Unicode verbatim regardless of ensure_ascii, but would weaken that documented global guarantee. ASCII content, existing escapes and whitespace are not normalized, and arbitrary raw content is still not validated. Maintainer preference on this compatibility tradeoff is welcome.

Closes #257

@lelit

lelit commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

I should focus on this, but one thing I wonder about is why we need to reimplement that encoding? Wouldn't it be more sensible to use writer->String()?

@lelit

lelit commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

I should focus on this, but one thing I wonder about is why we need to reimplement that encoding? Wouldn't it be more sensible to use writer->String()?

No, forget that, it would be clearly wrong.

@parijat1222q

parijat1222q commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor Author

@lelit , Thanks for taking a look. As you noted in your follow-up, writer->String() would change the meaning of the value. For example, raw {"key":"é"} should remain a JSON object, but String() would produce a JSON string containing that fragment instead.

The problem is that RawValue() transcodes non-ASCII characters to the ASCII output without JSON escaping. This fix keeps the raw-value path and, only when ensure_ascii=True, converts non-ASCII code points to JSON Unicode escapes before passing them to RawValue(). That preserves the object, existing ASCII spelling and whitespace. Astral characters use surrogate pairs; ensure_ascii=False keeps literal Unicode unchanged.

I chose this to preserve the JSON value while honoring the ASCII-output setting, without parsing and reserializing the fragment. The compatibility tradeoff is that non-ASCII spelling gets escaped under the default rather than remaining strictly verbatim. I'm happy to follow your preference on that. RawJSON still does not validate arbitrary raw input.

@lelit
lelit merged commit a48ee45 into python-rapidjson:master Nov 3, 2026
11 checks passed
@lelit

lelit commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

Thank you!

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.

RawJSON corrupts Unicode values with default ensure_ascii=True

2 participants