Skip to the content
GigaJSON
All tools
Format and check
View and query
Convert
JSON to code
Encode and decode
Sample files
Guides Open GigaJSON

Unescape JSON: fix strings full of backslashes

· 3 min read · By Amir Badi

You expected {"id":1} and got "{\"id\":1,\"note\":\"hi\"}": the same data wrapped in quotes, with a backslash before every inner quote. Nothing is lost. The JSON was turned into a string one time too many, and one more parse gets it back.

The quickest fix for a one-off: paste the string into JSON Escape, choose Unescape, and the JSON comes back indented. For code, logs and pipelines, here's why it happens and how to undo it in JavaScript, Python and jq. Every command below was run with Node 22, Python 3.12 and jq 1.7.

Why JSON gets escaped twice

JSON.stringify turns an object into JSON text. Call it again on that text and you get a JSON string containing JSON:

const o = { id: 1, note: "hi" };
JSON.stringify(o);                  // {"id":1,"note":"hi"}
JSON.stringify(JSON.stringify(o));  // "{\"id\":1,\"note\":\"hi\"}"

The second result is correct JSON for a string. The backslashes are there so the inner quotes don't end it early. Real code rarely calls it twice on one line. It happens across layers:

  • A web framework serializes the response for you, and the handler already passed it a string: res.json(JSON.stringify(data)) in Express, or jsonify(json.dumps(data)) in Flask.
  • A field holds JSON text on purpose. Message queues, webhooks and event logs often carry a payload that is itself serialized:
{"type":"order","payload":"{\"id\":7,\"total\":19.5}"}
  • A logger writes a JSON object as a string field inside its own JSON log line.

If you own the code that produces it, the real fix is to serialize once: pass the object, not a string. If you're reading someone else's data, unescape it.

Unescape in JavaScript

Parse the outer JSON, then parse the string field:

const event = JSON.parse(text);
typeof event.payload;               // "string"
JSON.parse(event.payload).total;    // 19.5

If the whole document is a string, parse twice: JSON.parse(JSON.parse(text)).

Unescape in Python

Same idea with json.loads:

import json
event = json.load(open("event.json"))
type(event["payload"]).__name__        # 'str'
json.loads(event["payload"])["total"]  # 19.5

Unescape with jq

jq's fromjson parses a string as JSON. Replace the field in place:

jq -c '.payload |= fromjson' event.json
{"type":"order","payload":{"id":7,"total":19.5}}

For a file that's one quoted string, jq 'fromjson' twice.json returns the object.

When the outer quotes are missing

Logs often print the inner text without its surrounding quotes:

{\"id\":1,\"note\":\"hi\"}

That isn't valid JSON on its own, so fromjson fails on it. Add the quotes back, parse it as a string, then parse the result. Put this in unquote.jq:

"\"" + . + "\"" | fromjson | fromjson

and run jq -Rc -f unquote.jq line.txt, which prints {"id":1,"note":"hi"}. (-R reads each line as raw text.) The JSON Escape tool does the same: in Unescape mode it accepts the text with or without the outer quotes.

Don't use find and replace

Deleting every backslash, or replacing \" with ", works on the example above and breaks on real data. A note containing a quote (She said \"hi\") or a Windows path (C:\\temp) has backslashes that belong to the inner string. Remove them blindly and the result is invalid JSON or changed values. A parser undoes exactly one level of escaping and nothing else.

What JSON actually requires you to escape

Inside a JSON string, only a few characters must be escaped:

Character Written as
Double quote \"
Backslash \\
Line break, carriage return, tab \n, \r, \t
Other control characters (U+0000 to U+001F) \u0001 and so on

Some things people expect to escape don't need it:

  • Single quotes. ' is an ordinary character in JSON, and \' is an error. JSON.parse rejects {"name":"O\'Brien"} with "Bad escaped character in JSON at position 11". If a single quote is giving you trouble, it's the shell, not JSON: in bash, write '{"name":"O'\''Brien"}'.
  • Slashes. \/ is allowed and reads back as /, but nothing requires it. PHP's json_encode writes it by default; JSON.stringify doesn't.
  • Accented letters and emoji. They can stay as they are. Python's json.dumps("café ✓") writes "caf\u00e9 \u2713" unless you pass ensure_ascii=False, which gives "café ✓". Both parse to the same string.

Escaping on purpose

Sometimes you need the escaped form: JSON in an environment variable, a test fixture, a string field of another JSON document, or a template that expects text. Use JSON.stringify(text) in JavaScript or json.dumps(text) in Python, or paste it into JSON Escape with Escape selected. With JSON input it compacts the JSON first, so {"id": 1} becomes the one-line string "{\"id\":1}".

If the escaped JSON you're unpacking is broken (cut off by a log line limit, for example), unescape it first, then run what's left through JSON Repair. The guide to fixing invalid JSON explains the usual errors.

Questions

How do I unescape a JSON string?

Parse it once more. In JavaScript, JSON.parse(text) gives you the inner JSON text and a second JSON.parse gives the object. In Python, json.loads twice. In jq, use fromjson. Or paste it into GigaJSON's JSON Escape tool with Unescape selected, and it comes back indented.

Why does my JSON have backslashes before every quote?

The JSON was turned into a string a second time, usually by calling JSON.stringify or json.dumps on text that was already JSON. The backslashes protect the inner quotes. The data is fine; it needs one more parse.

Should I remove the backslashes with find and replace?

No. Replacing \" with " breaks strings that really contain a quote or a backslash, such as a Windows path. Parse the string instead, so every escape is undone correctly.

Do I need to escape single quotes or slashes in JSON?

No. A single quote needs no escape in JSON, and \' is invalid. A forward slash may be written as \/ but doesn't have to be.

More from the blog