The Python JSON Module — Overview
Python's built-in jsonmodule handles parsing and serialising JSON. It ships with every Python 3 install — no pip needed. The four functions you'll use daily are:
json.loads(s)— parse a JSON string into a Python object (dict, list, etc.)json.load(f)— parse JSON from a file objectjson.dumps(obj)— serialise a Python object to a JSON stringjson.dump(obj, f)— serialise directly to a file object
Formatting happens in the "dump" family via keyword arguments. There is no separate "pretty print" function — the same dumps/dump you already use handles it.
Basic Pretty Printing with indent
import json
data = {
"name": "Alice",
"age": 30,
"hobbies": ["reading", "coding"],
"address": {
"city": "Berlin",
"zip": "10115"
}
}
# 2-space indent (JavaScript style)
print(json.dumps(data, indent=2))
# 4-space indent (Python PEP 8 style)
print(json.dumps(data, indent=4))
# Tab indent
print(json.dumps(data, indent="\t"))Output with indent=2:
{
"name": "Alice",
"age": 30,
"hobbies": [
"reading",
"coding"
],
"address": {
"city": "Berlin",
"zip": "10115"
}
}Formatting a Raw JSON String
If you already have JSON as a string (from an API response, a log line, or clipboard), parse and re-dump in one step:
import json
raw = '{"name":"Alice","age":30,"hobbies":["reading","coding"]}'
formatted = json.dumps(json.loads(raw), indent=2)
print(formatted)Formatting a JSON File
import json
# Read, format, and write back in one shot
with open('data.json', 'r', encoding='utf-8') as f:
data = json.load(f)
with open('data.json', 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2, ensure_ascii=False)
# Or format to a new file
with open('data.json') as src, open('data.pretty.json', 'w', encoding='utf-8') as dst:
json.dump(json.load(src), dst, indent=2, ensure_ascii=False)Command-Line JSON Formatting with json.tool
Python ships a command-line JSON formatter — the built-in json.tool module. Perfect for shell pipelines and one-off pretty-printing:
# Format a file to stdout
python3 -m json.tool data.json
# Format and write to a new file
python3 -m json.tool input.json output.json
# Format stdin (works with any pipe)
curl -s https://api.example.com/data | python3 -m json.tool
# Custom indent (Python 3.9+)
python3 -m json.tool --indent 4 data.json
# Sort keys (Python 3.6+)
python3 -m json.tool --sort-keys data.json
# Ensure ASCII disabled (Python 3.9+)
python3 -m json.tool --no-ensure-ascii data.jsonHandling Unicode with ensure_ascii
By default, json.dumps escapes all non-ASCII characters as \\uXXXXsequences. This produces valid JSON but is hard to read for internationalised data. Turn escaping off with ensure_ascii=False:
import json
data = {"name": "café", "city": "München", "greeting": "こんにちは"}
# Default — escapes non-ASCII
print(json.dumps(data, indent=2))
# {
# "name": "caf\u00e9",
# "city": "M\u00fcnchen",
# "greeting": "\u3053\u3093\u306b\u3061\u306f"
# }
# Preserve Unicode
print(json.dumps(data, indent=2, ensure_ascii=False))
# {
# "name": "café",
# "city": "München",
# "greeting": "こんにちは"
# }Important: when writing to a file with ensure_ascii=False, always open the file with encoding="utf-8". Otherwise the OS default codec (which on Windows can be cp1252) may fail on non-Latin characters.
Canonical JSON with sort_keys
sort_keys=True outputs dict keys in alphabetical order. This gives you canonical JSON — the same data always produces the same output regardless of dict insertion order. Essential for content-addressable storage, JSON hashing, and diffing.
import json
a = {"zip": "10115", "city": "Berlin", "name": "Alice"}
b = {"name": "Alice", "zip": "10115", "city": "Berlin"}
# Different insertion order → different output without sort_keys
print(json.dumps(a) == json.dumps(b)) # False
# Same output with sort_keys=True
print(json.dumps(a, sort_keys=True) == json.dumps(b, sort_keys=True)) # True
# Canonical output for hashing
import hashlib
canonical = json.dumps(a, sort_keys=True, ensure_ascii=False).encode('utf-8')
digest = hashlib.sha256(canonical).hexdigest()Compact JSON (No Indent, No Spaces)
When shipping JSON in HTTP responses or storing in a database, compact form saves bytes. Use no indent and tighten separators:
import json
data = {"a": 1, "b": [2, 3, 4]}
# Default separators — one space after :, ,
print(json.dumps(data))
# {"a": 1, "b": [2, 3, 4]}
# Tightest possible — no spaces
print(json.dumps(data, separators=(",", ":")))
# {"a":1,"b":[2,3,4]}Handling Non-Serialisable Types
json.dumps raises TypeError: Object of type X is not JSON serialisablefor anything it doesn't recognise — datetime, Decimal,set, custom classes. Two ways to handle it:
Quick fix: default=str
import json
from datetime import datetime
from decimal import Decimal
data = {
"when": datetime.now(),
"price": Decimal("19.99")
}
# str() everything json doesn't understand
print(json.dumps(data, indent=2, default=str))
# {
# "when": "2026-09-10 04:00:00.123456",
# "price": "19.99"
# }Precise: custom JSONEncoder
import json
from datetime import datetime, date
from decimal import Decimal
class RichEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, (datetime, date)):
return o.isoformat()
if isinstance(o, Decimal):
return float(o)
if isinstance(o, set):
return sorted(o)
return super().default(o)
data = {
"when": datetime.now(),
"price": Decimal("19.99"),
"tags": {"python", "json"}
}
print(json.dumps(data, indent=2, cls=RichEncoder))Faster Alternatives: orjson and ujson
The built-in json module is fine for most use cases but relatively slow. For performance-critical work, orjson is 2-10x faster and handles datetime, UUID, and dataclasses natively:
# pip install orjson
import orjson
data = {"name": "Alice", "age": 30}
# orjson.dumps returns bytes, not str
raw = orjson.dumps(data, option=orjson.OPT_INDENT_2)
print(raw.decode('utf-8'))
# orjson always produces UTF-8 and never escapes non-ASCII
data_utf = {"name": "café"}
print(orjson.dumps(data_utf, option=orjson.OPT_INDENT_2).decode('utf-8'))
# {
# "name": "café"
# }Common Pitfalls in Python JSON Formatting
- Forgetting encoding="utf-8" — on Windows the default codec is cp1252, which crashes on emojis and non-Latin scripts. Always specify encoding.
- Using str() to build JSON —
str(python_dict)produces output that looks like JSON but uses single quotes. Never valid JSON. Always usejson.dumps. - Assuming key order preservation — Python 3.7+ dicts preserve insertion order, but if you want output stability across systems, use sort_keys=True.
- Ignoring TypeError — catching the exception and continuing usually corrupts your data pipeline. Fix the encoder instead.
- Wrong tool for large files— for JSON > 1 GB, use
ijson(streaming parser).json.loadreads everything into memory.
Key Facts
- Built-in module:
- json (Python 3.x, no install needed)
- Pretty print:
- json.dumps(data, indent=2) or indent=4
- CLI tool:
- python3 -m json.tool file.json
- Unicode:
- Add ensure_ascii=False for readable non-ASCII output
- Faster libs:
- orjson (2-10x), ujson, simplejson
- Standard:
- RFC 8259 — same as JavaScript JSON.stringify
Related JSON Tools
- Format JSON Online — browser-based JSON formatter (paste + go)
- JSON Formatter JavaScript — same guide for the JS ecosystem
- Pretty Print JSON — general beautification guide
- Sort JSON Keys — deep dive on canonical JSON
- Validate JSON Online — check syntax before formatting
- Minify JSON Online — the opposite of pretty printing