2 min readUpdated 2026-10-10

Decode Double-Encoded JSON in an API Response

Identify a JSON object wrapped inside a JSON string, decode it in two steps, and fix the serializer that produced escaped quotes.

Maintained by Ali Aygör. Report a correction.

Check the parsed type before removing backslashes

A response containing escaped quotes is not necessarily broken. A JSON string is a valid JSON value. The important question is whether the API contract promises an object or a string. Inspect the raw response body and Content-Type rather than a log viewer's representation, which may add its own escaping.

If parsing the response once returns a string whose contents are another JSON document, the response is double encoded. Removing backslashes with a text replacement can corrupt escaped newlines, quotes inside names, or Windows paths. Parse each documented layer instead.

const body = JSON.stringify(JSON.stringify({ ok: true, count: 2 }));
const first = JSON.parse(body);
console.log(typeof first); // "string"
const result = JSON.parse(first);
console.log(result.ok); // true

Reproduce the problem with a small fixture

Copy a redacted response into JSON Validator. A successful syntax check only proves that the outer document is valid. Format it and inspect whether the whole value is quoted. Compare it with an expected object such as {"ok":true,"count":2}.

In JavaScript, response.json() already parses the response once. If it returns an object, do not call JSON.parse again. If it returns a string and the contract promises an object, parse that string once as a temporary compatibility step and report the server-side mismatch.

const first = await response.json();
const payload = typeof first === "string" ? JSON.parse(first) : first;
// Compatibility workaround: fix the server contract next.

Fix serialization at the boundary

A common cause is serializing an object manually and then handing the resulting string to a framework that serializes the response again. Return the object through the framework's JSON response mechanism, or send the serialized bytes directly with the correct content type. Avoid applying both operations.

Add a contract test that asserts the parsed top-level type as well as required fields. A status 200 check alone will miss double encoding. Include a string containing a quote and a backslash in the fixture so that a destructive text replacement cannot pass unnoticed.

Decoding does not decrypt JSON

JSON parsing interprets syntax; it does not recover encrypted values, reveal an API key's owner, or verify a token. A Base64 field needs a separate decoding step only when the contract says it is Base64. Treat arbitrary text as text until you know its representation.

Keep the raw fixture alongside the parsed result when comparing environments. This makes it possible to distinguish a server serialization change from a proxy, logging, or client display issue.

Specifications and references

Continue the investigation

Related tools

Browse all developer tools

Related workflows