Python Dict to JSON: Why You Get Single Quotes, True and None (and the Fix)
· 5 min read
Printing a Python dict with print(d), str(d) or an f-string gives Python syntax, with single quotes, True, False and None, which is not JSON. Use json.dumps(d) to produce real JSON with double quotes, true, false and null. If you only have the printed text, convert it with ast.literal_eval and then json.dumps, or paste it into the JSON fixer, which rewrites single quotes and Python literals and shows each change.
str(dict) versus json.dumps
import json
user = {"name": "O'Brien", "active": True, "manager": None, "tags": ("a", "b"), 1: "one"}
print(str(user))
# {'name': "O'Brien", 'active': True, 'manager': None, 'tags': ('a', 'b'), 1: 'one'}
print(json.dumps(user))
# {"name": "O'Brien", "active": true, "manager": null, "tags": ["a", "b"], "1": "one"}| Python value | str() / print() shows | json.dumps() writes |
|---|---|---|
"text" | 'text' (or "text" if it contains ') | "text" |
True / False | True / False | true / false |
None | None | null |
(1, 2) tuple | (1, 2) | [1, 2] |
{1: "a"} int key | {1: 'a'} | {"1": "a"} |
float("nan") | nan | NaN (not valid JSON; see below) |
{1, 2} set, datetime, Decimal, bytes | their repr | TypeError: Object of type ... is not JSON serializable |
Notice the mixed quoting in the first row: Python's repr switches to double quotes when a string contains an apostrophe. That detail is why simple find-and-replace fixes fail, as shown further down.
The errors this causes downstream
When printed dict text reaches a JSON parser, the error depends on the language. They all point at the first single quote or Python literal:
| Parser | Error for {'a': True} |
|---|---|
Python json.loads | JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1) |
Chrome / Node 20+ JSON.parse | SyntaxError: Expected property name or '}' in JSON at position 1 |
Firefox JSON.parse | SyntaxError: JSON.parse: expected property name or '}' at line 1 column 2 of the JSON data |
Python json.loads("True") | JSONDecodeError: Expecting value: line 1 column 1 (char 0) |
For the full list of Python messages, see Python JSONDecodeError explained; for the JavaScript side, Unexpected token in JSON.
Fix 1: serialize with json.dumps at the source
Wherever the text is produced (an API response, a file, a log line meant for machines, a message queue), replace str(d) with json.dumps(d). Useful options:
import json
from datetime import datetime
record = {"city": "Zürich", "at": datetime(2026, 11, 2, 9, 30), "ids": {3, 1, 2}}
json.dumps(record, default=str)
# {"city": "Z\u00fcrich", "at": "2026-11-02 09:30:00", "ids": "{1, 2, 3}"}
def to_jsonable(o):
if isinstance(o, datetime):
return o.isoformat()
if isinstance(o, set):
return sorted(o)
raise TypeError(f"Object of type {type(o).__name__} is not JSON serializable")
json.dumps(record, default=to_jsonable, ensure_ascii=False, indent=2)
# {
# "city": "Zürich",
# "at": "2026-11-02T09:30:00",
# "ids": [1, 2, 3]
# }Write files with json.dump(data, f) (no s) and open them with encoding="utf-8". In web frameworks, return data through the framework's JSON helper (Flask's jsonify, Django's JsonResponse, FastAPI's automatic serialization) rather than formatting a string yourself.
Fix 2: convert a dict string you already have
If the text was already printed, logged or stored, parse it as Python, then dump it as JSON. ast.literal_eval evaluates Python literals only (strings, numbers, tuples, lists, dicts, sets, booleans and None), so it does not run arbitrary code the way eval does:
import ast
import json
text = "{'name': \"O'Brien\", 'active': True, 'manager': None}"
data = ast.literal_eval(text)
print(json.dumps(data))
# {"name": "O'Brien", "active": true, "manager": null}Know its limits before relying on it:
- It raises
ValueError: malformed node or stringwhen the repr contains objects, for exampledatetime.datetime(2026, 11, 2, 0, 0)orDecimal('1.5'). Those values cannot be recovered without custom handling. - Python's documentation warns that very large or deeply nested input can exhaust memory or the C stack and crash the interpreter. Do not feed it untrusted input of unbounded size.
- Never use plain
evalas a shortcut. It executes any code in the string. - Text that is mostly JSON but contains
true/nulland single quotes at the same time is neither valid Python nor valid JSON, and both parsers reject it.
Why replace("'", '"') is a trap
The popular one-liner json.loads(text.replace("'", '"')) works on toy examples and corrupts real data:
text = "{'name': \"O'Brien\", 'ok': True}" # what print(d) produced
text.replace("'", '"')
# {"name": "O"Brien", "ok": True}
# ^ the apostrophe now ends the string; True is still not JSONApostrophes inside values turn into string terminators, double-quoted strings from the repr get mangled, and True/None are left untouched. Regex variants fail the same way on apostrophes. Use ast.literal_eval, or a tokenizer-based repair that understands string boundaries.
Fix 3: repair it in the browser
When the dict text is in a ticket, a log or a chat and you just need valid JSON, paste it into the JSON fixer. It converts single-quoted strings and keys, True, False and None, and trailing commas, and lists every change in a diff so you can check what it did. It runs locally, so log data with customer details is not uploaded. If the text came from an LLM, Fix LLM JSON also removes Markdown fences and surrounding prose.
Going the other way: JSON to a Python dict
json.loads(text) returns a dict with True, False and None restored. Keys that were integers in Python come back as strings, and tuples come back as lists, because JSON has no such types. If you want typed classes instead of plain dicts, JSON to Python generates dataclasses or Pydantic models from a sample. For a broader tour of what breaks JSON, see the common JSON errors hub.
FAQ
How do I convert a Python dict to JSON?
Call json.dumps(your_dict) for a string or json.dump(your_dict, file) to write a file. Add indent=2 to pretty-print and default=str for values such as datetimes.
Why does my JSON have single quotes instead of double quotes?
It is not JSON: it is a Python dict printed with print(), str() or an f-string. Replace that call with json.dumps(), which always uses double quotes.
How do I convert single quotes to double quotes in JSON?
Do not use a plain string replace, because it breaks on apostrophes. Parse the text with ast.literal_eval and dump it with json.dumps, or use a JSON fixer that understands string boundaries.
Why does Python print True and None while JSON needs true and null?
They are Python's spellings of the same values. json.dumps converts True, False and None to true, false and null, and json.loads converts them back.
Is ast.literal_eval safe?
It only evaluates literals, so it cannot run code like eval. It can still exhaust memory or the stack on huge or deeply nested input, so limit the size of untrusted strings.

