Zerowidth CleanerAll measured guides

CLAUDE WATERMARK REMOVER · PRACTICAL TEST

Can JSON.stringify return text that JSON.parse rejects?

Yes with our indentation argument. A space plus U+200B produces invalid JSON; removing the marker leaves valid indentation. The unchanged xx and numeric-character indent controls remain invalid, and long string indentation is truncated to ten units.

Tested October 14, 2026 · v24.19.0 · 4 measured runs

Open the Claude text cleaner · Full measured data

Code-point comparison for Marked indent

Measured inputs and outputs

These are locally constructed test strings, not evidence that Claude inserts these characters. We executed the saved homepage script snapshot with a minimal DOM harness and compared exact strings. The invisible-checkbox state and dash mode appear in each row. The observations below test this page's specific question. Destination observations run independently on the exact input and cleaned strings in the recorded Node runtime. We make no detector-score or statistical-watermark removal claim.

Before and after observations; full code points and strings are in the JSON download.
Fixture and modeInput string and code pointsOutput and cleaner statusObserved before / after
Marked indent
direct string / keep / invisible true
" ​"
U+0020 U+200B
" "
Removed 1 invisible character
{"serialized":"{\n ​\"a\": {\n ​ ​\"b\": 1\n ​}\n}","firstIndent":" ​","indentUnits":2,"parses":false,"errorName":"SyntaxError"}
{"serialized":"{\n \"a\": {\n \"b\": 1\n }\n}","firstIndent":" ","indentUnits":1,"parses":true,"errorName":null}
Space indent
direct string / keep / invisible true
" "
U+0020 U+0020
" "
No selected invisible characters found
{"serialized":"{\n \"a\": {\n \"b\": 1\n }\n}","firstIndent":" ","indentUnits":2,"parses":true,"errorName":null}
{"serialized":"{\n \"a\": {\n \"b\": 1\n }\n}","firstIndent":" ","indentUnits":2,"parses":true,"errorName":null}
Non-whitespace indent
direct string / keep / invisible true
"xx"
U+0078 U+0078
"xx"
No selected invisible characters found
{"serialized":"{\nxx\"a\": {\nxxxx\"b\": 1\nxx}\n}","firstIndent":"xx","indentUnits":2,"parses":false,"errorName":"SyntaxError"}
{"serialized":"{\nxx\"a\": {\nxxxx\"b\": 1\nxx}\n}","firstIndent":"xx","indentUnits":2,"parses":false,"errorName":"SyntaxError"}
Long indent
direct string / keep / invisible true
"123456789012"
U+0031 U+0032 U+0033 U+0034 U+0035 U+0036 U+0037 U+0038 U+0039 U+0030 U+0031 U+0032
"123456789012"
No selected invisible characters found
{"serialized":"{\n1234567890\"a\": {\n12345678901234567890\"b\": 1\n1234567890}\n}","firstIndent":"1234567890","indentUnits":10,"parses":false,"errorName":"SyntaxError"}
{"serialized":"{\n1234567890\"a\": {\n12345678901234567890\"b\": 1\n1234567890}\n}","firstIndent":"1234567890","indentUnits":10,"parses":false,"errorName":"SyntaxError"}

A string indentation argument can insert invalid syntax

The supplied string is passed as the third argument of JSON.stringify on the same small nested object in every row. Before cleanup the space-plus-marker fixture inserts that spelling before member names at each nesting level, and parsing the resulting export throws SyntaxError. After cleanup only the ordinary space remains and the exported JSON parses. The two-space control succeeds both times. The xx control remains invalid, because the cleaner does not remove its visible letters. The long digit string inserts only its first ten UTF-16 units at the first nesting level and repeats that selected gap deeper in the object. It still fails parsing. We record the serialized output and first indent, making the effect inspectable without treating successful stringification as syntax validation.

Export formatting is a different stage from cleaning a JSON document

Our earlier structural-whitespace guide starts with a complete JSON text and asks which incoming separator characters its parser accepts. This page starts with an indentation option and generates a new document afterward. The cleaner processes only that option string; it never receives the authored object or the exported JSON. Numeric-looking characters inside our long control are still a string argument, so this test does not measure the numeric space argument’s separate clamping behavior. We also do not exercise surrogate pairs at the ten-unit boundary, custom replacers or toJSON hooks. The fixed object has two nesting levels solely to show how an accepted gap is repeated, not to represent a production configuration or a document imported from Claude.

Use permitted whitespace and validate the actual serialized output

If pretty-printed text is meant to remain interoperable JSON, choose an indentation policy using JSON-compatible whitespace and test the final document with its intended parser. A returned JavaScript string is not by itself proof that it is valid JSON. Cleanup resolves the selected marker case but cannot repair arbitrary visible indentation like xx. Preserve the chosen option with the export when diagnosing a failure, and compare nested lines as well as the first line. The downloaded reproducer executes generation and parsing independently for each exact string. It does not normalize the document after export or infer that every parser accepts the same extension. Avoid applying broad character deletion to the complete payload before identifying whether the formatting option is the actual source of invalid syntax.

Reproduce this test

Save reproduce.cjs and tested-app.js in the same folder. Run the command below with Node.js. The harness prints its runtime, script SHA-256 and every measured row. Compare those rows with the original record. Using a newer script or runtime creates a new experiment; retain the version information with your rerun.

node reproduce.cjs

Reference and next check

ECMAScript JSON parsing and serialization provides the relevant primary definition. The table and fixture analysis are original measurements. For broader inspection, use our Unicode inspector. Read the scope distinction before interpreting cleanup as a watermark result.