Just File Tools

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.

Uses tool: String to JSON

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 text or varchar for JSON data because jsonb didn'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.stringify on a value, then the wire format (often itself JSON) wraps the result, calling JSON.stringify on the whole envelope.
  • A database column was written via INSERT INTO logs (payload) VALUES (JSON.stringify(JSON.stringify(...))) — usually because someone called stringify in both the application and the DB driver.
  • A logger's serializer ran stringify on 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

Try it now: String to JSON

Unescape stringified JSON — paste once, get a real object back

Open String to JSON