JSON Formatter Python β json.dumps() Guide
Use the free online JSON Formatter & Beautifier directly in your browser β no install required.
Try JSON Formatter & Beautifier Online βWhen I'm debugging a Python API client, the first thing I reach for is python pretty print json β one call to json.dumps(data, indent=4) and an unreadable single-line blob becomes instantly navigable. Python's built-in json module handles this entirely in the standard library β no third-party install required. If you just need a quick one-off result without writing code, the online JSON Formatter handles it instantly. This guide covers every practical method for use in scripts and applications: json.dumps() with all its parameters, pprint, orjson for high-performance formatting, the json.tool CLI, and real-world scenarios like formatting API responses and reading from disk β all with Python 3.8+ compatible code. It also covers serializing custom types like datetime and UUID, streaming gigabyte-scale files with ijson, and terminal syntax highlighting with rich.
- β
json.dumps(data, indent=4)is built into Python's stdlib since 2.6 β no install needed. - βPass
ensure_ascii=Falseany time your data contains accented letters, CJK characters, or emoji. - βFor
datetime,UUID, or custom classes use thedefault=parameter or subclassjson.JSONEncoder. - β
separators=(',', ':')strips all whitespace β use for network transfer or URL embedding. - β
orjsonis 5β10Γ faster than the stdlib and natively handlesdatetimeanduuid.UUID. - β
pprint.pprint()outputs Python syntax (True/None), not valid JSON β never use for files or API responses. - βFor JSON files larger than 50 MB, stream with
ijsoninstead ofjson.load()to avoidMemoryError.
What is JSON Pretty Printing?
Pretty printing transforms a dense, minified JSON string into a human-readable format with consistent indentation and line breaks. The transformation is purely cosmetic: the data is identical, only the presentation changes. Python's json module handles this entirely in the standard library β nothing to install.
{"id":"usr_9f3a2b","name":"Maria Garcia","roles":["admin","editor"],"prefs":{"theme":"dark","lang":"en"}}{
"id": "usr_9f3a2b",
"name": "Maria Garcia",
"roles": [
"admin",
"editor"
],
"prefs": {
"theme": "dark",
"lang": "en"
}
}json.dumps() β The Standard Way to Format JSON
json.dumps() is part of Python's standard library since Python 2.6 β just import json, no install needed. It serializes any JSON-compatible Python object to a formatted string. The key parameter is indent: set it to 4 (or 2) to get readable output.
import json
user = {
"id": "usr_9f3a2b",
"name": "Maria Garcia",
"roles": ["admin", "editor"],
"prefs": {"theme": "dark", "lang": "en"}
}
print(json.dumps(user, indent=4))
# Output:
# {
# "id": "usr_9f3a2b",
# "name": "Maria Garcia",
# "roles": [
# "admin",
# "editor"
# ],
# "prefs": {
# "theme": "dark",
# "lang": "en"
# }
# }For production use you'll often want sort_keys=True (consistent output across runs) and ensure_ascii=False (keep non-ASCII characters readable):
import json
api_response = {
"timestamp": "2024-05-01T10:30:00Z",
"status": "success",
"data": {
"user_id": "usr_9f3a2b",
"display_name": "MarΓa GarcΓa",
"score": 4892.5,
"tags": ["python", "backend", "api"]
}
}
print(json.dumps(api_response, indent=4, sort_keys=True, ensure_ascii=False))
# Output (keys sorted, accents preserved):
# {
# "data": {
# "display_name": "MarΓa GarcΓa",
# "score": 4892.5,
# "tags": ["api", "backend", "python"],
# "user_id": "usr_9f3a2b"
# },
# "status": "success",
# "timestamp": "2024-05-01T10:30:00Z"
# }json.dumps() returns a string. To write formatted JSON directly to a file, use json.dump(data, f, indent=4) (without the s) β it writes to a file object and avoids creating an intermediate string in memory.json.dumps() Parameters Reference
All parameters are optional except the object itself. The defaults produce compact, ASCII-safe JSON β pass parameters explicitly for human-readable output.
Compact JSON Output with the separators Parameter
By default json.dumps() separates items with ", " and keys from values with ": ". The separators parameter overrides both. Passing (',', ':') strips all whitespace to produce the most compact valid JSON possible β useful for network transmission, URL embedding, or storing JSON in a database column where every byte counts.
import json
payload = {
"endpoint": "/api/v2/events",
"filters": {"status": "active", "limit": 100},
"sort": "desc"
}
# Default β spaces after separators (readable)
default_out = json.dumps(payload)
# {"endpoint": "/api/v2/events", "filters": {"status": "active", "limit": 100}, "sort": "desc"}
# len = 88
# Compact β no whitespace at all
compact_out = json.dumps(payload, separators=(',', ':'))
# {"endpoint":"/api/v2/events","filters":{"status":"active","limit":100},"sort":"desc"}
# len = 80 (9% smaller; savings grow on larger, deeply nested payloads)
# Compact + sorted keys for reproducible cache keys or content hashes
canonical = json.dumps(payload, separators=(',', ':'), sort_keys=True)
print(canonical)
# {"endpoint":"/api/v2/events","filters":{"limit":100,"status":"active"},"sort":"desc"}indent= alongside separators=, the separators argument controls only the inline separators β newlines and indentation from indent are preserved. If you want compact single-line output, omit indent (or pass None) and set separators=(',', ':').Serializing Custom Python Objects with the default Parameter
The standard json module serializes dicts, lists, strings, numbers, booleans, and None β but raises a TypeError on any other type. The two most common culprits in production code are datetime objects and UUIDs.
import json
from datetime import datetime, timezone
import uuid
order = {
"order_id": uuid.uuid4(), # β TypeError: UUID is not JSON serializable
"placed_at": datetime.now(timezone.utc), # β TypeError: datetime is not JSON serializable
"total_usd": 142.50,
"items": ["pro-subscription", "addon-storage"]
}
json.dumps(order) # raises TypeErrorApproach 1 β the default= parameter
Pass a callable to default=. json.dumps() calls it for any object it cannot handle. Return a serializable representation, or raise TypeErrorfor types you don't explicitly support β never silently swallow unknowns.
import json
from datetime import datetime, timezone, date
import uuid
from decimal import Decimal
def json_default(obj):
if isinstance(obj, (datetime, date)):
return obj.isoformat()
if isinstance(obj, uuid.UUID):
return str(obj)
if isinstance(obj, Decimal):
return float(obj)
raise TypeError(f"Type {type(obj).__name__!r} is not JSON serializable")
order = {
"order_id": uuid.uuid4(),
"placed_at": datetime(2024, 5, 1, 10, 30, 0, tzinfo=timezone.utc),
"total_usd": Decimal("142.50"),
"items": ["pro-subscription", "addon-storage"]
}
print(json.dumps(order, indent=4, default=json_default))
# {
# "order_id": "a3f1c2d4-e5b6-7890-abcd-ef1234567890",
# "placed_at": "2024-05-01T10:30:00+00:00",
# "total_usd": 142.5,
# "items": ["pro-subscription", "addon-storage"]
# }Approach 2 β subclass json.JSONEncoder
For reusable encoding logic shared across multiple modules, subclassing json.JSONEncoder is cleaner than threading a default function everywhere. Override the default method and call super().default(obj) as the final fallback β this preserves correct error behavior for unsupported types.
import json
from datetime import datetime, timezone
import uuid
from decimal import Decimal
class AppEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime):
return obj.isoformat()
if isinstance(obj, uuid.UUID):
return str(obj)
if isinstance(obj, Decimal):
return float(obj)
return super().default(obj) # raises TypeError for unknown types
order = {
"order_id": uuid.uuid4(),
"placed_at": datetime(2024, 5, 1, 10, 30, 0, tzinfo=timezone.utc),
"total_usd": Decimal("142.50"),
}
# Pass the encoder class via cls=
print(json.dumps(order, indent=4, cls=AppEncoder))
# Identical output to the default= approach abovesuper().default(obj) (or raise TypeError explicitly) for unrecognized types. Silently returning str(obj) for everything will corrupt objects that should have raised an error β a bug that is hard to trace in production.Decoding back β object_hook
Encoding is only half the story. To reconstruct a custom Python object from JSON, pass an object_hook function to json.loads() or json.load(). The hook is called for every decoded JSON object (dict) and can return any Python value in its place β giving you a full encode β decode round-trip.
import json
from datetime import datetime
from dataclasses import dataclass
@dataclass
class Event:
name: str
occurred_at: datetime
user_id: str
def encode_event(obj):
if isinstance(obj, Event):
return {
"__type__": "Event",
"name": obj.name,
"occurred_at": obj.occurred_at.isoformat(),
"user_id": obj.user_id,
}
raise TypeError(f"Cannot serialize {type(obj)}")
def decode_event(d):
if d.get("__type__") == "Event":
return Event(
name=d["name"],
occurred_at=datetime.fromisoformat(d["occurred_at"]),
user_id=d["user_id"],
)
return d
# Encode
event = Event("login", datetime(2024, 5, 1, 10, 30), "usr_9f3a2b")
json_str = json.dumps(event, default=encode_event, indent=4)
# Decode back to an Event instance
restored = json.loads(json_str, object_hook=decode_event)
print(type(restored)) # <class 'Event'>
print(restored.occurred_at) # 2024-05-01 10:30:00object_hook is called for every nested dict in the document β not just the top level. Include a discriminator field (like "__type__") so the hook can distinguish your custom objects from plain dicts that should be left as-is.pprint β The Alternative Module (and When Not to Use It)
Python's pprint module (pretty printer) formats Python data structures for readability in the terminal. It works on parsed Python objects, not on JSON strings β and its output uses Python syntax, not JSON syntax.
import json, pprint
raw = '{"sensor_id":"s-441","readings":[23.1,23.4,22.9],"unit":"celsius","active":true}'
data = json.loads(raw)
# pprint β valid Python repr, NOT valid JSON
pprint.pprint(data, sort_dicts=False)
# {'sensor_id': 's-441',
# 'readings': [23.1, 23.4, 22.9],
# 'unit': 'celsius',
# 'active': True} β Python True, not JSON true
# json.dumps β valid JSON
print(json.dumps(data, indent=4))
# {
# "sensor_id": "s-441",
# "readings": [23.1, 23.4, 22.9],
# "unit": "celsius",
# "active": true β valid JSON
# }pprint output to an API endpoint or write it to a .json file β it will break any JSON parser that expects standard syntax. Use json.dumps(indent=4) for all output that must be valid JSON.When pprint does make sense: quick terminal inspection of Python objects in a REPL or debug log, especially when the object contains types that are not JSON-serializable (sets, custom class instances, dataclasses before conversion).
How to Pretty Print a JSON Response from Requests
The most common real-world scenario: you have a JSON file on disk or an HTTP response from an API, and you want to format it for debugging or logging. Both cases use the same approach β parse to a Python dict, then format with json.dumps().
Reading from a file
import json
try:
with open("config.json", "r", encoding="utf-8") as f:
data = json.load(f)
# Pretty-print to console
print(json.dumps(data, indent=4, ensure_ascii=False))
# Or write the formatted version back to disk
with open("config.pretty.json", "w", encoding="utf-8") as f:
json.dump(data, f, indent=4, ensure_ascii=False)
except json.JSONDecodeError as e:
print(f"Invalid JSON: {e}")
except FileNotFoundError:
print(f"File not found: config.json")Formatting an API response
import json, requests
from requests.exceptions import HTTPError, ConnectionError, Timeout
def pretty_print_api(url: str) -> None:
try:
resp = requests.get(url, timeout=10)
resp.raise_for_status()
print(json.dumps(resp.json(), indent=4, ensure_ascii=False))
except HTTPError as e:
print(f"HTTP {e.response.status_code}: {e}")
except (ConnectionError, Timeout) as e:
print(f"Network error: {e}")
except json.JSONDecodeError:
print(f"Response body is not JSON:\n{resp.text[:500]}")
pretty_print_api("https://api.github.com/repos/python/cpython")response.json() already parses the response body β no need to call json.loads() separately. Always add raise_for_status() before accessing .json() to catch 4xx/5xx errors before they cause a confusing parse error.Command-Line Pretty Printing
Python ships with json.tool, a CLI module for formatting JSON straight from the terminal β no Python script required. It's available on any machine with Python installed.
# Format a local file
python -m json.tool config.json
# Pipe API response through formatter
curl -s https://api.github.com/users/gvanrossum | python -m json.tool
# Format from stdin
echo '{"service":"api-gateway","version":"2.1.0","healthy":true}' | python -m json.tool
# Sort keys alphabetically
python -m json.tool --sort-keys data.json
# Custom indent (Python 3.9+)
python -m json.tool --indent 2 data.json--indent and --no-indent flags. For more powerful terminal JSON filtering, consider jq β but python -m json.tool covers the formatting use case with zero extra dependencies.If you're not on a terminal at all β pasting a response from Postman or a log file β the ToolDeck JSON Formatter lets you paste, format, and copy in one step, with syntax highlighting and validation built in.
Alternative Libraries: orjson and rich
orjson β 5β10Γ Faster with Native Type Support
The standard json module is fast enough for most use cases, but if you're serializing thousands of objects per second β logging pipelines, high-throughput APIs, large data exports β orjson is 5β10Γ faster. It also natively handles types the standard library can't serialize without a custom default function: datetime, uuid.UUID, numpy arrays, and dataclasses.
pip install orjson
import orjson
from datetime import datetime, timezone
import uuid
event = {
"event_id": uuid.uuid4(), # no str() needed β orjson handles UUID
"timestamp": datetime.now(timezone.utc), # no isoformat() needed
"service": "auth-service",
"level": "INFO",
"payload": {
"user_id": "usr_9f3a2b",
"action": "login",
"ip": "192.168.1.42",
"latency_ms": 34
}
}
# orjson.dumps returns bytes; .decode() converts to str
print(orjson.dumps(event, option=orjson.OPT_INDENT_2).decode())
# {
# "event_id": "a3f1c2d4-e5b6-7890-abcd-ef1234567890",
# "timestamp": "2024-05-01T10:30:00+00:00",
# "service": "auth-service",
# ...
# }Two things to know: orjson.dumps() returns bytes, not a string β call .decode() if you need a string. It only supports 2-space indentation via OPT_INDENT_2; for 4-space output use the standard json.dumps(indent=4).
rich β Syntax Highlighting in the Terminal
If you regularly inspect JSON in a terminal or REPL, rich renders color-coded, syntax-highlighted output that makes deeply nested structures readable at a glance. Keys, strings, numbers, and booleans each get a distinct color β far easier to scan than a wall of monochrome text. It's a debug-time tool only, not for production serialization.
pip install rich
from rich import print_json
import json
# print_json() accepts a JSON string
raw = '{"event":"login","user_id":"usr_9f3a2b","timestamp":"2024-05-01T10:30:00Z","success":true,"meta":{"ip":"192.168.1.42","attempts":1}}'
print_json(raw)
# To pretty-print a Python dict, convert to string first
data = {
"status": "success",
"count": 42,
"tags": ["python", "api", "backend"]
}
print_json(json.dumps(data))rich.print_json() outputs ANSI escape codes for terminal colour β never capture this output and write it to a .json file or send it as an API response. Use json.dumps(indent=4) for any machine-readable output.simplejson β Drop-in Compatibility Replacement
simplejson is the library that became Python's standard json module β it's still maintained independently and stays ahead of the stdlib on minor features. It's a true drop-in replacement: swap the import and the rest of your code is unchanged. Useful when you need Decimal support without a custom encoder, or when targeting older Python environments.
pip install simplejson
import simplejson as json # identical API to the stdlib
from decimal import Decimal
order = {
"item": "API subscription",
"price": Decimal("49.99"), # stdlib json raises TypeError here
"quantity": 3,
}
# simplejson serializes Decimal natively β no default= needed
print(json.dumps(order, indent=4, use_decimal=True))
# {
# "item": "API subscription",
# "price": 49.99,
# "quantity": 3
# }orjson is the better choice. Reach for simplejson when you need native Decimal serialization without writing a custom encoder, or when maintaining a codebase that already uses it.Processing Large JSON Files Without Running Out of Memory
json.load() reads the entire file into memory before you can access a single field. On a file with millions of records or a payload over a gigabyte, that causes a MemoryError β or at best forces the process to swap to disk and crawl.
Streaming with ijson
ijson is a streaming JSON parser that generates items one at a time from a file object. You iterate over array elements without ever holding the full dataset in memory β peak memory stays proportional to a single object, not the file size.
pip install ijson
import ijson
from decimal import Decimal
# events.json structure: {"events": [...millions of objects...]}
total_revenue = Decimal("0")
login_count = 0
with open("events.json", "rb") as f: # ijson requires binary mode
for event in ijson.items(f, "events.item"):
if event.get("type") == "purchase":
total_revenue += Decimal(str(event["amount_usd"]))
elif event.get("type") == "login":
login_count += 1
print(f"Revenue: ${total_revenue:.2f} | Logins: {login_count}")
# Processes a 2 GB file with ~30 MB peak memory usagejson.load() to ijson when your file exceeds roughly 50β100 MB. Below that threshold json.load() is simpler and significantly faster because it uses a C-extension parser internally. Above 100 MB, the memory savings from streaming outweigh the extra overhead.NDJSON β one JSON object per line
NDJSON (Newline Delimited JSON, also called JSON Lines or .jsonl) stores one complete JSON object per line. Log exporters, Kafka consumers, and data pipelines frequently produce this format because each line can be appended and read independently β no need to parse the entire file to add a record. The standard library handles it without any extra dependencies.
import json
from pathlib import Path
# Write NDJSON β one event per line
events = [
{"ts": "2024-05-01T10:00:00Z", "user": "usr_9f3a2b", "action": "login"},
{"ts": "2024-05-01T10:01:03Z", "user": "usr_9f3a2b", "action": "purchase", "sku": "pro-plan"},
{"ts": "2024-05-01T10:15:42Z", "user": "usr_4ab1d9", "action": "login"},
]
with open("events.ndjson", "w", encoding="utf-8") as f:
for event in events:
f.write(json.dumps(event, ensure_ascii=False) + "\n")
# Read NDJSON β constant memory, no matter how large the file
purchase_count = 0
with open("events.ndjson", "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line: # skip blank lines
continue
event = json.loads(line)
if event.get("action") == "purchase":
purchase_count += 1
print(f"{event['ts']} β {event['user']} bought {event['sku']}")Common Mistakes
I've seen these four mistakes in almost every code review involving JSON serialisation β especially from developers coming from JavaScript where JSON.stringify handles encoding automatically.
Problem: print() on a dict uses Python repr β output shows True/None (Python syntax), not true/null (JSON syntax). It's not valid JSON.
Fix: Always use json.dumps(data, indent=4) for valid, readable JSON output.
data = {"active": True, "count": None}
print(data)
# {'active': True, 'count': None}print(json.dumps(data, indent=4))
# {
# "active": true,
# "count": null
# }Problem: Special characters (accented letters, CJK, emoji) get escaped to \\uXXXX sequences, making the output unreadable.
Fix: Pass ensure_ascii=False to preserve the original Unicode characters.
user = {"name": "MarΓa GarcΓa"}
json.dumps(user, indent=2)
# {"name": "Mar\u00eda Garc\u00eda"}json.dumps(user, indent=2, ensure_ascii=False)
# {"name": "MarΓa GarcΓa"}Problem: json.dumps() returns a string; you then need a separate f.write() call, creating an unnecessary intermediate string.
Fix: Use json.dump(data, f, indent=4) β it writes directly to the file object.
with open("out.json", "w") as f:
f.write(json.dumps(data, indent=4))with open("out.json", "w", encoding="utf-8") as f:
json.dump(data, f, indent=4, ensure_ascii=False)Problem: pprint.pprint() uses Python syntax (True, None, single quotes) which JSON parsers reject.
Fix: Use json.dumps(indent=4) for any output that must be parseable as JSON.
import pprint
pprint.pprint({"running": True, "last_error": None})
# {'running': True, 'last_error': None}import json
print(json.dumps({"running": True, "last_error": None}, indent=4))
# {"running": true, "last_error": null}Method Comparison β json.dumps, orjson, simplejson, rich
Use json.dumps() for everyday formatting and file writes β it covers 95% of use cases with zero dependencies. Reach for orjson when serializing in a hot path or when your objects include datetime and UUID fields. Use simplejson when you need drop-in stdlib compatibility with Decimal support out of the box. Reserve rich.print_json() and pprint strictly for local terminal inspection β neither produces machine-readable output.
Frequently Asked Questions
How do I pretty print JSON in Python?
Call json.dumps(data, indent=4). The indent parameter sets the number of spaces per nesting level. Import the json module first β it ships with Python's standard library so no pip install is needed. Pass ensure_ascii=False if your data contains non-ASCII characters such as accented letters or CJK characters.
import json
user = {"username": "jsmith", "plan": "enterprise", "permissions": ["read", "write", "deploy"]}
print(json.dumps(user, indent=4))What is the difference between json.dumps() and json.dump()?
json.dumps() (with an "s") returns a formatted string in memory. json.dump() (without "s") writes directly to a file-like object β pass the open file handle as the second argument. For writing formatted JSON to disk, json.dump(data, f, indent=4) is idiomatic and avoids creating an intermediate string.
# dumps β string in memory
formatted = json.dumps(data, indent=4)
# dump β write directly to file
with open('output.json', 'w', encoding='utf-8') as f:
json.dump(data, f, indent=4)Why does json.dumps() show \u0441\u0442\u0440 instead of the actual character?
By default ensure_ascii=True escapes every non-ASCII character to a \uXXXX sequence. Set ensure_ascii=False to preserve the original Unicode characters. This is especially important for names, addresses, and any user-generated content in non-Latin scripts.
data = {"city": "ΠΠΎΡΠΊΠ²Π°", "greeting": "δ½ ε₯½"}
# Default β escaped
json.dumps(data, indent=4)
# {"city": "\u041c\u043e\u0441\u043a\u0432\u0430", ...}
# Readable
json.dumps(data, indent=4, ensure_ascii=False)
# {"city": "ΠΠΎΡΠΊΠ²Π°", "greeting": "δ½ ε₯½"}How do I pretty print a JSON string (not a dict)?
First parse the string with json.loads(), then format with json.dumps(). The two calls can be chained in one line for quick terminal inspection.
import json
raw = '{"endpoint":"/api/v2/users","timeout":30,"retry":true}'
print(json.dumps(json.loads(raw), indent=4))Can I use pprint to format JSON in Python?
pprint.pprint() produces Python object representation, not valid JSON. It uses True/False/None (Python syntax) instead of true/false/null (JSON syntax). Never pass pprint output to an API or JSON parser β use json.dumps(indent=4) for anything that must be valid JSON.
import pprint, json
data = {"active": True, "score": None}
pprint.pprint(data) # {'active': True, 'score': None} β not JSON
json.dumps(data, indent=4) # {"active": true, "score": null} β valid JSONHow do I sort JSON keys alphabetically in Python?
Add sort_keys=True to json.dumps(). For the command line, use python -m json.tool --sort-keys data.json. Sorted keys make JSON diffs readable and help spot changed values at a glance.
import json
server = {"workers": 4, "host": "0.0.0.0", "port": 8080, "debug": False}
print(json.dumps(server, indent=4, sort_keys=True))
# {
# "debug": false,
# "host": "0.0.0.0",
# "port": 8080,
# "workers": 4
# }Working with JSON in Python gives you full control β custom serializers, streaming, pipeline integration. When you just need to inspect or share a formatted snippet, ToolDeck's JSON Formatter is the faster path: paste your JSON and get an indented, highlighted result with no environment setup.
Related Tools
Maria is a backend developer specialising in Python and API integration. She has broad experience with data pipelines, serialisation formats, and building reliable server-side services. She is an active member of the Python community and enjoys writing practical, example-driven guides that help developers solve real problems without unnecessary theory.
Dmitri is a DevOps engineer who relies on Python as his primary scripting and automation language. He builds internal tooling, CI/CD pipelines, and infrastructure automation scripts that run in production across distributed teams. He writes about the Python standard library, subprocess management, file processing, encoding utilities, and the practical shell-adjacent Python that DevOps engineers use every day.