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.
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.