Notes · Sep 5, 2026

# Double-encoded JSON: how a payload ends up inside a string, and how to read it

Nobody double-encodes JSON on purpose. It happens one hop at a time, and each hop is reasonable on its own.

## How it happens

A producer serialises an event to JSON and publishes it. The transport wraps it in its own envelope — SNS puts it in Message, SQS in Body, a Kafka client in a string value — and serialises the envelope as JSON too, so the inner document becomes a string field with every quote escaped. A consumer logs the envelope; the logging library serialises the log record as JSON; now there are two layers of backslashes. A database column of type text stores the same bytes and adds none — but the ORM that reads it back will happily serialise the string once more.

```
{
  "Type": "Notification",
  "MessageId": "8f3aa19c-4d21-4e6b-9c02-fe13a1d04b7e",
  "TopicArn": "arn:aws:sns:eu-west-1:123456789012:checkout-events",
  "Message": "{\"event\":\"stripe.charge.completed\",\"order_id\":\"ord_8f3aa19c4d\",\"amount\":17054,\"currency\":\"usd\",\"customer\":{\"id\":\"cus_4129\",\"email\":\"rin.aoyama@oboro.studio\"}}",
  "Timestamp": "2026-04-21T14:32:08.412Z",
  "MessageAttributes": {
    "payload": { "Type": "String", "Value": "{\"retries\":2,\"source\":\"webhook\"}" }
…
```

## Recognising the layers

One layer shows as \" around keys and values inside a string. Two layers show as \\\" — a backslash escaping a backslash escaping a quote. The rule of thumb: JSON.parse once per layer, and if the result is still a string full of backslashes, parse again. It is tedious by hand and easy to get wrong when the layers are not uniform: an SNS notification’s MessageAttributes carry their own values as strings beside the Message, at a different depth.

## Opening it in place

unstringify treats a string that parses as JSON, XML or YAML as a packed value. Every such string shows Unpack on its row — a finding about your data, not a mode. The SNS sample above holds 2: the Message body, and the payload attribute inside MessageAttributes. Unpack opens each where it sits, names the parser that opened it, and repeats until nothing inside parses, up to 8 levels. Pack restores the original bytes rather than re-serialising from the tree, so the string you send back is the string you received.

Over records — an NDJSON log whose payload field is a string, the Kibana export’s message cell — the value opens in the panel as nested rows and the source cell stays as written. What it will not do: decode base64, URL-encoding or gzip. A value has to be text that parses.

## Try it

## Try it on the file that broke your afternoon.

free while in alpha · no signup

---

Canonical: https://unstringify.com/notes/double-encoded-json
