How to unescape stringified JSON (paste from a log, get the real object)
When a JSON value gets stored as a string inside another JSON document, you need to unescape it before you can read it. Here is the fix in one click.
Stringified JSON is one of those bugs that doesn't look like a bug. The data is
valid. Every parser accepts it without complaint. But what you expected to be an
object — keys, values, nesting — comes back as a string with backslashes everywhere.
You parse it again with your eyes and try to read the actual structure underneath
all those \" escapes. The fix is one extra JSON.parse, but
only if you spot it.
What "stringified JSON" actually is
Every JSON value is one of: object, array, string, number, boolean, null. A stringified JSON value is one where the value you wanted (typically an object) has been serialized to a string and stored as a string inside another JSON document. The data looks like this:
{
"event": "user.signed_up",
"payload": "{\"user\":{\"id\":42,\"email\":\"alex@example.com\"}}"
}
The payload field is a string containing JSON. To use the payload, you
have to call JSON.parse on the string a second time. Most code that
handles this correctly does it explicitly:
const payload = JSON.parse(envelope.payload);. Code that doesn't ends
up with a string that looks like an object but isn't.
Why this happens in real systems
Stringified JSON appears anywhere JSON data crosses a serialization boundary that doesn't preserve structure:
- Logging libraries. Most loggers (Winston, Bunyan, Pino, etc.) serialize complex values to strings before writing to disk. Logs are then read back as strings — JSON-shape inside.
- Message queues. Many queues (SQS, Pub/Sub, Kafka) accept string messages. Publishers serialize payloads before publishing; consumers parse them. If a consumer logs the raw message, the next reader sees stringified JSON.
- Database text columns. Old schemas use
textorvarcharfor JSON data becausejsonbdidn't exist when the schema was designed. SELECT returns a string; the application is expected to parse. - Webhook payloads. Many webhook senders serialize the inner payload and embed it as a string inside a wrapping envelope.
The fix
Use our String to JSON tool. Paste the stringified value (or the whole envelope with the stringified field), click the field, and you get the parsed real JSON back. The tool tracks how many levels of stringification it had to unwrap and shows the count — useful for spotting when your data pipeline is double-encoding.
For one-off cases where you just want to format a stringified blob, the JSON Formatter on this site auto-detects stringified input. Paste anything JSON-shaped; if it's wrapped in extra layers of stringification, the formatter unwraps them and shows you the actual value with a "Auto-detected and unwrapped 2 levels" banner.
Examples
Level 1 (regular JSON). Input: {"foo":"bar"}. Output:
{"foo": "bar"} (formatted). Levels unwrapped: 1.
Level 2 (singly stringified). Input:
"{\"foo\":\"bar\"}". Output: {"foo": "bar"}. Levels
unwrapped: 2.
Level 3 (doubly stringified — uncommon but does happen). Input:
"\"{\\\"foo\\\":\\\"bar\\\"}\"". Output: {"foo": "bar"}.
Levels unwrapped: 3.
Raw escaped string without outer quotes. Input:
{\"foo\":\"bar\"} (escape sequences but no enclosing quotes — the format
you get if you copy a JSON value out of source code). The tool detects the escape
sequences and re-adds the outer quotes before parsing.
Diagnosing double-encoding in production
If our tool reports level 3 or higher on a payload you pulled from your system, something in your pipeline is double-encoding. Common causes:
-
A producer calls
JSON.stringifyon a value, then the wire format (often itself JSON) wraps the result, callingJSON.stringifyon the whole envelope. -
A database column was written via
INSERT INTO logs (payload) VALUES (JSON.stringify(JSON.stringify(...)))— usually because someone calledstringifyin both the application and the DB driver. -
A logger's serializer ran
stringifyon a value, then the log aggregator's pipeline did it again at ingest.
Each layer is recoverable (the tool peels them off), but the underlying bug is worth fixing at the source — every consumer pays the unwrap cost otherwise.
Code equivalent
The logic the tool runs is essentially:
let value = input.trim();
let levels = 0;
while (typeof value === 'string' && levels < 10) {
try {
value = JSON.parse(value);
levels++;
} catch {
break;
}
} Keep parsing while the result is still a string. Stop when you reach a non-string (object, array, number, boolean, null) or when a parse fails. Cap at 10 levels to prevent pathological input from infinite-looping. This is all the tool does — you can do the same thing in a Node REPL or Python interpreter if you prefer.
Edge cases the tool handles
- BOM-prefixed input. The U+FEFF byte order mark is stripped before parsing.
- Whitespace-only input. Reported as empty, not parsed as null.
- Primitive values.
"42"unwraps to the number 42 (not the string "42"). Same for booleans and null. - Deeply nested objects. The recursion is at the string level, not the object level — a 10-level-deep nested object is one string-parse operation, not ten.
- Unicode escapes.
édecodes correctly to é. - Invalid JSON. If the input isn't parseable as JSON at any level, the tool surfaces the parser's exact error message.
Privacy considerations
The JSON often contains real production data — API responses, log entries with
user IDs, internal payloads. The in-browser tool uses native
JSON.parse with no network step. Verify in DevTools by watching the
Network panel stay empty during parse. The parsed result lives in your tab and
nowhere else; close the tab and it's gone.
Related tools and guides
- String to JSON — the tool this guide covers.
- JSON Formatter — auto-detects stringified input too.
- JSON Diff — once unwrapped, compare two versions.
- Data Format Converter — convert JSON ↔ YAML / TOML / XML.
Try it now: String to JSON
Unescape stringified JSON — paste once, get a real object back
Open String to JSON