Why a Python dict isn't JSON
They look nearly identical, which is exactly why this trips people up. Print a dict in Python, or paste a boto3 response out of a terminal, and you get something that reads like JSON but no parser will accept:
{'Bucket': 'invoices-prod', 'Public': False, 'Owner': None}Four things in there are illegal JSON:
| Python | JSON |
|---|---|
'single quotes' |
"double quotes" |
True / False |
true / false |
None |
null |
| Trailing commas | Not allowed at all |
Paste that into a strict validator and you get Expecting property name enclosed in double quotes - technically correct, and no help at all when you
just want to read the thing.
The fastest way to convert it
Paste it into the JSON Formatter and hit Format. There's no setting to find first: if the input isn't valid JSON, it gets repaired and then formatted in one step.
{
"Bucket": "invoices-prod",
"Public": false,
"Owner": null
}Above the box you'll see exactly what changed - "Converted single-quoted strings to double quotes", "Converted Python True/False/None to JSON true/false/null". That list matters more than it sounds like it should, which is the next section.
The part most converters get wrong
The obvious way to build this is find-and-replace: swap every ' for ",
every True for true, and call it done. It works on the example above and
quietly destroys real data.
Here's the same approach meeting a string that contains the word True:
{'msg': 'True story, bro'}Replace every True and you get {"msg": "true story, bro"}. The text changed.
Nothing errored, nothing warned you, and if you'd pasted a hundred records
you'd never spot it. The same bug hits runs of spaces ('hello world'
collapsing to 'hello world') and any apostrophe in a value - "it's fine"
becomes "it"s fine", which then fails to parse for a reason that has nothing
to do with your input.
The fix isn't a better regex. A regex can't tell "inside a string" from "outside a string", and that distinction is the entire problem. So the formatter walks the input character by character, tracking when it's inside a string literal, and only ever repairs structure. Your values come out the same way they went in:
{
"msg": "True story, bro"
}Capital T intact.
Pasting AWS output
AWS CLI output is already valid JSON, so it formats as-is. The awkward case is a printed boto3 response, which is a Python dict carrying types JSON has never heard of:
{'Name': 'invoices-prod', 'Created': datetime.datetime(2026, 9, 21, 0, 0), 'Size': Decimal('1.5')}Those get handled specifically: datetime.datetime(...) is kept as a string so
you don't lose the timestamp, and Decimal('1.5') becomes the number 1.5.
{
"Name": "invoices-prod",
"Created": "datetime.datetime(2026, 9, 21, 0, 0)",
"Size": 1.5
}And if you've pasted several objects back to back - a DynamoDB export or CloudWatch Logs output, where each line is its own JSON object - they're combined into a single array rather than rejected as "extra data".
What else it repairs
Same rule throughout: structure only, and everything it did is listed.
- Unquoted keys -
{name: "Ana"} - Unquoted values -
{"city": Paris} - Trailing commas
//and/* */comments- Curly quotes from a doc or a Slack message
- Missing commas between values
- Brackets left unclosed
NaNandInfinity, which JSON has no value for, becomenull
Input that's already valid JSON is never rewritten at all - it goes straight to formatting, so there's no chance of a repair touching a document that didn't need one.
Doing it in Python instead
If this is part of a script rather than a one-off, you don't need a tool:
import json
json.dumps({'Bucket': 'invoices-prod', 'Public': False}, indent=2)json.dumps handles the quote and literal conversion for you. It will raise
TypeError on datetime and Decimal values, which is where default=str
earns its keep:
json.dumps(response, indent=2, default=str)Reach for the browser tool when you've got a dict pasted from somewhere else and just want to read it - not when you're writing code that already has the object in hand.
Nothing is uploaded
The whole thing runs in your browser. Nothing you paste is sent to a server, which is the point when the dict you're debugging is a production API response with customer records in it.