Javascript is required
PitfallsPublished 2026-07-289 min read

Nested JSON Escaping: Backslashes and Parsing

A payload such as {"payload":"{\"name\":\"Tiny\"}"} usually does not indicate broken escaping. It means JSON text has been stored inside another JSON string. Every string-syntax boundary must escape quotes and backslashes again. This article focuses on representation layers, not general JSON syntax or Unicode escape semantics.

By: Tiny
JSON转义反斜杠JSON.stringify嵌套 JSON

1. Distinguish values, JSON text, and source literals

The same data can be an in-memory object, JSON.stringify output, or a JavaScript string literal that represents that output. The visible slash count depends on which layer a tool is showing.

Check typeof, length, and direct console output rather than relying only on an inspector preview.

const value = { message: 'He said "hello"', path: 'C:\\temp\\a.txt' };
const jsonText = JSON.stringify(value);

console.log(typeof value);    // object
console.log(typeof jsonText); // string
console.log(jsonText);

2. More layers require more escaping

If an outer payload field stores inner JSON text, stringify the inner object and then stringify the outer object. The second serialization protects the quotes and backslashes inside the embedded text. Those extra characters belong to the current representation, not the final business value.

const payloadObject = { name: 'Tiny', path: 'C:\\temp\\a.txt' };
const envelopeObject = {
  event: 'save',
  payload: JSON.stringify(payloadObject),
};
const requestBody = JSON.stringify(envelopeObject);
console.log(requestBody);

3. Parse exactly once per documented string layer

Parse the outer request first. The payload remains a string until the contract says it contains serialized JSON. Do not repeatedly parse every string until parsing fails; ordinary user text such as “123” or “true” can also be valid JSON and would silently change type.

type Envelope = { event: string; payload: string };
type Payload = { name: string; path: string };

const requestBody = JSON.stringify({
  event: 'save',
  payload: JSON.stringify({ name: 'Tiny', path: 'C:\\temp\\a.txt' }),
});
const outer = JSON.parse(requestBody) as Envelope;
const inner = JSON.parse(outer.payload) as Payload;

console.log(inner.path); // C:\\temp\\a.txt

4. Serialize objects instead of concatenating JSON

Manual concatenation breaks on quotes, backslashes, line breaks, and control characters. Construct values as objects and let JSON.stringify escape each layer.

For APIs you control, prefer a nested object over a stringified payload. Keep double encoding only when a queue field, database text column, signature format, or third-party contract requires it.

const payload = { query: 'name = "Tiny"', path: 'C:\\data' };

const preferredBody = JSON.stringify({ event: 'search', payload });
const legacyBody = JSON.stringify({
  event: 'search',
  payload: JSON.stringify(payload),
});

5. Use field-specific compatibility parsing

If a legacy field may be either an object or a JSON object string, normalize that field once at the boundary and validate the result shape. Do not recursively inspect every string in the response.

type JsonObject = Record<string, unknown>;

function parseObjectField(value: unknown): JsonObject {
  if (value && typeof value === 'object' && !Array.isArray(value)) {
    return value as JsonObject;
  }
  if (typeof value !== 'string') {
    throw new TypeError('payload must be an object or JSON object text');
  }
  const parsed: unknown = JSON.parse(value);
  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
    throw new TypeError('payload JSON must contain an object');
  }
  return parsed as JsonObject;
}

6. Why global backslash replacement corrupts data

Removing every backslash damages Windows paths, regex text, newline sequences, and legitimate literal backslashes. Replacing escaped quotes without knowing the current layer is equally unsafe. Preserve the raw text and use the workflow in /articles/json-unexpected-token when parsing fails.

  • Do not remove all backslashes.
  • Do not repeatedly JSON.parse every string.
  • Do not infer a JSON object only because text starts with {.
  • Do not confuse an inspector preview with the network payload.

7. Boundaries and verification

Test quotes, backslashes, line breaks, tabs, empty strings, null, arrays, and Unicode. Success means the restored value deep-equals the original value, not that a log appears to contain fewer slashes.

For signed or hashed protocols, define which exact representation is signed. Object semantics, inner JSON text, and outer request bytes are different inputs.

const original = {
  quote: '"',
  slash: '\\',
  newline: '\n',
  nested: { text: '中文 🚀' },
};

const text = JSON.stringify({ payload: JSON.stringify(original) });
const outer = JSON.parse(text) as { payload: string };
const restored = JSON.parse(outer.payload);

console.assert(JSON.stringify(restored) === JSON.stringify(original));

8. FAQ and conclusion

Why do Postman, Network, and console show different slash counts? They may show bytes, JSON text, or an escaped debugger preview.

Can payload always be an object? Usually for your own API, but some storage and third-party contracts require text.

Is \u4e2d the same problem? Not necessarily; it is a JSON Unicode escape covered by /articles/unicode-json-escapes.

The rule is one stringify per encoded layer and one parse per documented string layer. Avoid extra stringification when an object can be transmitted directly, and use /tools/json/json-compress-escape to inspect representation changes.

Sources and further reading

Specifications and references for checking the article’s technical details.

Related articles

Keep exploring

Use the related browser tool to test the workflow with your own sample data.

Open related tool