When I’m debugging an API response or reviewing a config export, raw JSON can feel like staring at a wall of text. One missing brace and your eyes glaze over. Pretty printing is the small step that makes JSON readable, shareable, and safe to reason about—especially when you’re comparing versions, reviewing logs, or writing unit tests. I’ve seen teams lose hours to a misplaced comma simply because the output was compacted into a single line. I treat pretty printing as a default habit rather than a special trick.
You’ll get a clear, modern playbook for pretty printing JSON in Python: when to use it, when not to, how to format consistently across tools, and how to keep it fast in real systems. I’ll cover json.dumps, file workflows, custom encoders, handling datetimes and Decimals, and dealing with huge payloads without melting your terminal. You’ll also see common mistakes I watch for in code reviews and a few patterns I’ve adopted in 2026 workflows where AI copilots help draft data transformations but humans still need clarity when verifying output.
Why pretty printing matters in real work
Pretty printing is not just for tutorials. I use it in three recurring situations: debugging, change reviews, and client-facing exports. When your API returns 2000 lines of nested data, the raw single-line JSON is useless. Pretty output helps you spot what changed between two versions of a payload, makes diffs smaller and more reliable, and lowers the risk of shipping bad data.
I also see a pragmatic compliance angle. If you’re validating that personally identifiable fields are removed before logging, you need to visually scan the output. A compact payload hides those issues. Pretty printing gives you a readable snapshot you can review in seconds.
Another reason: your future self. I can’t count how many times I’ve opened an old log file from a production incident and felt relieved it was readable. The few milliseconds saved by compact output are rarely worth the hours wasted in analysis later.
The core tool: json.dumps with indent
Python’s built-in json module is enough for most cases. The simplest pattern is json.dumps(data, indent=2) for strings or json.dump(data, file, indent=2) for files. I typically use an indent of 2 or 4 depending on the project’s style guide. Two spaces fit well in logs; four spaces work better in docs and config files.
Here’s a complete, runnable example using a realistic employee payload, with a note about indentation consistency:
import json
json_data = ‘[{"Employee ID":1,"Name":"Abhishek","Designation":"Software Engineer"},‘ \
‘{"Employee ID":2,"Name":"Garima","Designation":"Email Marketing Specialist"}]‘
jsonobject = json.loads(jsondata)
I prefer indent=2 for logs because it stays compact but readable.
print(json.dumps(json_object, indent=2))
If you change indent from 2 to 4, the output becomes more spread out. There’s no magic number; the best choice is the one your team sticks to. I usually standardize this in a shared json_format.py or in a logging helper so nobody argues about tabs versus spaces at 2 a.m.
Key formatting knobs you should know
indent is the main lever, but there are a few other settings I reach for:
sort_keys=True: Keeps the output stable by sorting keys alphabetically. I use this for diffs and tests.ensure_ascii=False: Preserves Unicode characters. If you work with names like “Søren” or “Zoë,” this matters.separators=(‘,‘, ‘: ‘): Controls spacing around separators. Useful if you want compact output but still readable.
Example with deterministic ordering for tests:
import json
data = {
"region": "North America",
"team": "Platform",
"active": True,
"members": ["Ava", "Noah", "Maya"]
}
print(json.dumps(data, indent=2, sort_keys=True))
I use this pattern in snapshot tests so diffs are clean. If keys are random, your test snapshots become noise.
Pretty printing JSON from files
Reading from a file is common in ETL pipelines, config validation, and log analysis. I prefer context managers because they reduce file descriptor leaks and errors. This is a good baseline pattern:
import json
with open("employees.json", "r", encoding="utf-8") as f:
data = json.load(f)
print(json.dumps(data, indent=2))
If you want to write the pretty output back to a file, just use json.dump:
import json
payload = {
"emp1": {"name": "Lisa", "designation": "programmer", "age": 34, "salary": 54000},
"emp2": {"name": "Elis", "designation": "Trainee", "age": 24, "salary": 40000},
}
with open("employees_pretty.json", "w", encoding="utf-8") as f:
json.dump(payload, f, indent=2, sort_keys=True)
Notice that I store numeric fields as numbers, not strings. It’s a subtle habit that pays off when you’re later filtering or aggregating data.
Handling non-JSON types (datetime, Decimal, Path)
The json module only knows about basic types: dict, list, str, int, float, bool, and None. Real-world data often includes datetimes, Decimals, or file paths. If you try to dump those directly, you’ll get a TypeError. I solve this by creating a custom encoder.
import json
from datetime import datetime, timezone
from decimal import Decimal
from pathlib import Path
class PrettyEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime):
return obj.astimezone(timezone.utc).isoformat()
if isinstance(obj, Decimal):
# Convert to string to avoid float precision issues
return str(obj)
if isinstance(obj, Path):
return str(obj)
return super().default(obj)
payload = {
"invoice_id": "INV-2026-0042",
"issued_at": datetime(2026, 1, 26, 14, 30, tzinfo=timezone.utc),
"total": Decimal("199.95"),
"attachment": Path("/data/invoices/INV-2026-0042.pdf")
}
print(json.dumps(payload, indent=2, cls=PrettyEncoder))
I recommend using Decimal for currency and keeping the value as a string in JSON to avoid floating point confusion. When I review bugs around rounding, most originate from float conversions that looked harmless at the time.
An encoder pattern I reuse across projects
I often wrap the encoder plus settings in a single helper. This keeps my projects consistent and lets me extend behavior without hunting through the codebase.
import json
from datetime import datetime, date, timezone
from decimal import Decimal
from pathlib import Path
class SafeJSONEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, (datetime, date)):
if isinstance(obj, datetime):
obj = obj.astimezone(timezone.utc)
return obj.isoformat()
if isinstance(obj, Decimal):
return str(obj)
if isinstance(obj, Path):
return str(obj)
return super().default(obj)
def pretty_json(obj) -> str:
return json.dumps(
obj,
indent=2,
ensure_ascii=False,
sort_keys=True,
cls=SafeJSONEncoder,
)
This version also handles date objects and enforces UTC for datetime. It’s small, but it eliminates repeated boilerplate in every script.
Dealing with large JSON payloads without freezing your tools
Pretty printing a 200MB payload can make your terminal sluggish or even crash your editor. I’ve run into this in data exports and log analysis. Here are two strategies I use:
1) Stream and preview
Instead of loading everything into memory, I preview the top-level structure and a few items. That keeps memory usage manageable.
import json
with open("big_payload.json", "r", encoding="utf-8") as f:
data = json.load(f)
Safely preview the first 2 entries if data is a list
if isinstance(data, list):
preview = data[:2]
else:
# Preview a few keys if data is a dict
preview = {k: data[k] for k in list(data.keys())[:3]}
print(json.dumps(preview, indent=2))
2) Use log-friendly compacting with separators
When I need readable output but can’t afford lots of whitespace, I use compact separators with a small indent or no indent. It’s a middle ground.
import json
print(json.dumps(data, indent=1, separators=(",", ": ")))
This keeps arrays readable without adding too many lines. I usually do this inside debug logs so I can still scan without bloating log files.
3) Chunked writing for long lists
If you have a huge list of items, you can write a JSON array manually in chunks to avoid holding it all in memory at once. This is more advanced but useful for exports.
import json
items = [{"id": i, "value": i * 2} for i in range(5)]
with open("chunked.json", "w", encoding="utf-8") as f:
f.write("[\n")
for i, item in enumerate(items):
if i > 0:
f.write(",\n")
f.write(json.dumps(item, indent=2))
f.write("\n]\n")
This isn’t perfect for all cases, but it’s a simple way to keep output pretty without a huge memory spike.
Common mistakes I see (and how I avoid them)
I do a lot of code review, and these are the recurring issues that cause confusion:
1) Pretty printing and then re-parsing: Some developers call json.dumps and immediately json.loads to “clean” data. That’s wasted work and can introduce subtle type changes. I only pretty print for output, not for transformations.
2) Forgetting ensureascii=False: If your data includes non-English names, pretty output becomes full of escape codes. I explicitly set ensureascii=False when I expect international text.
3) Accidental string types: People read JSON as strings and then dump the string itself, which results in extra quotes and escaped characters. If you see output like "{\"id\": 1}", you’re pretty printing a string instead of a parsed object.
4) Huge indentation in logs: I’ve seen logs with indent=8. It looks nice but makes logs enormous and slower to ship. I standardize indent=2 for logs, indent=4 for documents.
5) Non-deterministic output in tests: Snapshot tests fail randomly when key order changes. I always set sort_keys=True in tests.
If you want a single safe default that avoids most of these errors, use this:
def pretty_json(obj) -> str:
return json.dumps(obj, indent=2, ensureascii=False, sortkeys=True)
When to pretty print vs when not to
Pretty printing is a tool, not a default for every output. I decide based on the audience and the medium.
Use pretty printing when:
- You’re debugging or troubleshooting
- You’re writing human-facing documentation
- You’re producing config or fixture files
- You’re generating a diff or a snapshot test
Avoid pretty printing when:
- You’re streaming high-volume logs in production
- You need to keep output size minimal (like mobile data sync)
- You’re measuring latency in high-throughput services
I’ll still log compact JSON in production, but I’ll add a “pretty mode” toggle or a feature flag for deep debugging. That way I can switch on readability when I need it without paying the cost every time.
Traditional vs modern workflows (2026 perspective)
The core mechanics of pretty printing haven’t changed, but the workflows around it have. AI assistants can generate JSON transformations quickly, but I always verify the output manually before trusting it. Pretty printing becomes my “visual checksum.”
Here’s a clear comparison that I use when helping teams decide how to handle JSON formatting in their pipelines:
Traditional Pattern
When I Choose It
—
—
print(json.dumps(obj, indent=2))
prettyjson(obj) helper + editor snippet Quick checks, local experiments
Unsorted dumps in snapshots
sortkeys=True with stable formatting CI stability and predictable diffs
Compact JSON, unreadable
Production safety with on-demand clarity
Manual scripts
Rapid iteration with verificationEven with smarter tooling, I stick to a simple rule: readable output first, minimal output only when there’s a performance reason.
Performance considerations you should account for
Pretty printing adds whitespace, and that means bigger output and more CPU time. The overhead is usually modest for small objects, but it adds up with big payloads.
Here’s how I think about it in practice:
- Small objects (dozens of keys): overhead is negligible, typically 1–5ms in local tests.
- Medium payloads (thousands of keys): overhead can be noticeable, typically 10–30ms depending on hardware.
- Large payloads (hundreds of thousands of keys): overhead can be seconds. I avoid pretty printing unless it’s critical.
If you need both performance and readability, consider:
- pretty printing only in debug mode
- pretty printing a subset of the payload
- sampling requests (e.g., 1 in 100 logs)
I treat pretty printing as a readability feature, and I pay for it only when I get the benefit back.
A simple, practical micro-benchmark
I don’t over-optimize pretty printing, but if I’m curious I’ll run a quick benchmark using timeit.
import json
import timeit
payload = {"key": list(range(1000)), "nested": {"a": 1, "b": 2, "c": 3}}
print(timeit.timeit(lambda: json.dumps(payload), number=1000))
print(timeit.timeit(lambda: json.dumps(payload, indent=2), number=1000))
This isn’t production-grade benchmarking, but it gives you a directional feel for the cost of whitespace. If the difference is irrelevant, I keep the code readable.
Practical patterns I use in production code
Below are two small patterns that show how I integrate pretty printing without creating chaos.
1) A dedicated formatter utility
This keeps all formatting in one place, making it easy to change later.
import json
PRETTYJSONINDENT = 2
def pretty_json(obj) -> str:
return json.dumps(
obj,
indent=PRETTYJSONINDENT,
ensure_ascii=False,
sort_keys=True,
)
I keep this in a utils/json_format.py module and import it whenever I need readable JSON. It’s a small layer, but it prevents duplication.
2) Logger integration with a debug flag
I often have a logger that can switch between compact and pretty output.
import json
DEBUG_JSON = False
def log_json(obj):
if DEBUG_JSON:
print(json.dumps(obj, indent=2, ensure_ascii=False))
else:
print(json.dumps(obj, separators=(",", ":"), ensure_ascii=False))
You can plug this into a real logging framework instead of print. I keep it simple here because the idea matters more than the library.
Edge cases that surprise people
Here are a few tricky cases I’ve seen in real systems:
- NaN and Infinity: Python allows them by default, but JSON standard does not. Use
allow_nan=Falseto enforce strict compliance. If you seeValueError: Out of range float values are not JSON compliant, that’s why. - Circular references: If an object references itself,
json.dumpswill blow up. You need to pre-process the data or use a custom serializer. - Mixed key types: JSON keys must be strings. If you build dicts with non-string keys, Python will coerce them, often in unexpected ways.
Example with strict float handling:
import json
import math
data = {"score": math.nan}
try:
print(json.dumps(data, allow_nan=False))
except ValueError as e:
print("JSON compliance error:", e)
I enable allow_nan=False in data exports to avoid writing invalid JSON to downstream systems.
Handling circular references safely
There’s no built-in json solution for circular references, but I sometimes sanitize data with a helper that replaces repeats with a marker.
import json
class SafeCycleEncoder(json.JSONEncoder):
def init(self, args, *kwargs):
super().init(args, *kwargs)
self._seen = set()
def default(self, obj):
obj_id = id(obj)
if objid in self.seen:
return ""
self.seen.add(objid)
return json.JSONEncoder.default(self, obj)
This is intentionally simple and not suited for all objects, but it prevents infinite recursion in messy debugging situations.
Pretty printing in interactive workflows
If you’re working in notebooks or REPL sessions, you can use json.dumps directly, but I prefer a helper that auto-prints. It reduces repetitive typing.
import json
def pp(obj):
print(json.dumps(obj, indent=2, ensureascii=False, sortkeys=True))
pp({"project": "Phoenix", "status": "active", "members": ["Kai", "Riya"]})
In Jupyter, I often use IPython.display.JSON for richer formatting, but the core principle stays the same: readability without surprises.
A small REPL-friendly trick
I sometimes add a one-liner in my REPL session that binds pretty printing to a short name so I can use it constantly while exploring data:
import json
pp = lambda obj: print(json.dumps(obj, indent=2, ensureascii=False, sortkeys=True))
It’s not fancy, but it makes interactive exploration smoother.
Putting it all together: a complete example
This example shows a realistic workflow: read a JSON file, normalize some fields, pretty print with stable ordering, and write the result back. It’s simple but represents a pattern I use when cleaning API outputs before committing them to version control.
import json
from datetime import datetime, timezone
INPUTPATH = "rawexport.json"
OUTPUTPATH = "prettyexport.json"
with open(INPUT_PATH, "r", encoding="utf-8") as f:
data = json.load(f)
Normalize a timestamp if it exists
if "exportedat" in data and isinstance(data["exportedat"], str):
data["exported_at"] = datetime.now(timezone.utc).isoformat()
with open(OUTPUT_PATH, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensureascii=False, sortkeys=True)
I like this pattern because it makes the output consistent and version-control friendly.
New: practical scenarios you can reuse today
Pretty printing becomes more valuable when you treat it as part of a workflow, not just a quick trick. Here are some scenarios I rely on.
Scenario 1: Debugging API responses with selective visibility
Sometimes I don’t want the entire payload, just the shape and a few entries. I use a trimming helper so I can keep output readable without deleting data.
import json
def preview(obj, listitems=2, dictkeys=4):
if isinstance(obj, list):
return obj[:list_items]
if isinstance(obj, dict):
return {k: obj[k] for k in list(obj.keys())[:dict_keys]}
return obj
response = {
"status": "ok",
"meta": {"page": 1, "limit": 50},
"items": [{"id": i, "name": f"Item {i}"} for i in range(10)]
}
print(json.dumps(preview(response), indent=2))
This lets me quickly validate structure without drowning in details.
Scenario 2: Comparing two JSON payloads in tests
I often compare payloads by pretty printing them and writing them to temp files. That way, if a test fails, the diff is clear.
import json
def stable_dump(obj, path):
with open(path, "w", encoding="utf-8") as f:
json.dump(obj, f, indent=2, sortkeys=True, ensureascii=False)
expected = {"a": 1, "b": [2, 3]}
actual = {"b": [2, 3], "a": 1}
stable_dump(expected, "expected.json")
stable_dump(actual, "actual.json")
Even if the data is logically the same, the formatting ensures you can see real differences without noise.
Scenario 3: Prettifying JSON from a command line pipe
Sometimes you don’t want to write a full script. If you have a JSON file in a pipeline, you can pretty print it quickly with a one-liner:
import json, sys
print(json.dumps(json.load(sys.stdin), indent=2, ensure_ascii=False))
I keep this in a small script so I can do: cat data.json | python pretty.py.
New: deeper explanation of formatting controls
Most people use indent and stop there. But the other knobs can matter in subtle ways.
separators and whitespace control
The default separator is (‘, ‘, ‘: ‘). If you want compact output that still feels readable, you can tighten the comma spacing and keep a space after :.
import json
data = {"id": 1, "name": "Ravi", "active": True}
print(json.dumps(data, separators=(",", ": ")))
That yields something like {"id":1,"name":"Ravi","active":true} in a single line. If you add indent, the separators still matter for how trailing spaces are handled on each line.
sort_keys for deterministic diffs
I treat sort_keys=True as a policy decision. It’s great for tests and configs, but it can be surprising when you want to preserve semantic order. For example, if your JSON represents a UI schema where order matters, sorting keys might hurt readability.
I decide based on intent:
- Order matters for humans: keep original order, avoid sorting.
- Order matters for diffs/tests: sort keys.
- Order matters for display: keep original order but make a note in docs.
ensure_ascii for internationalization
When ensureascii=True (the default), non-ASCII characters become escape codes. It’s valid JSON, but it hides the data. I treat ensureascii=False as a best practice unless I’m outputting to a strict ASCII-only system.
New: production guardrails and conventions
In larger codebases, consistency is everything. Here are a few guardrails I’ve adopted to keep JSON output safe and predictable.
1) Centralize formatting policy
I keep a single formatter in a shared module. That ensures every team member and script uses the same rules.
import json
DEFAULTJSONOPTIONS = {
"indent": 2,
"sort_keys": True,
"ensure_ascii": False,
}
def format_json(obj, overrides):
opts = {DEFAULTJSONOPTIONS, overrides}
return json.dumps(obj, opts)
This makes it trivial to change indentation or sorting across the entire project.
2) Separate human output from machine output
If an API endpoint serves machines, I don’t pretty print. If a file is meant for humans, I do. Mixing the two makes systems brittle.
3) Explicit flags for debug output
I use a debug flag rather than relying on ad hoc decisions. That gives me a controlled way to enable heavy formatting when needed.
New: parsing pitfalls and how to spot them quickly
I’ve seen plenty of subtle mistakes when people handle JSON in a hurry. These quick checks save time:
Check 1: Is it already a dict/list?
If your pretty print looks like a quoted blob, you probably printed a JSON string rather than parsed data.
import json
raw = ‘{"a": 1}‘
obj = json.loads(raw)
print(json.dumps(obj, indent=2)) # correct
print(json.dumps(raw, indent=2)) # incorrect, prints escaped string
Check 2: Are numbers actually numbers?
If you see quotes around your numbers, they’re strings. That’s almost always unintentional.
Check 3: Are booleans Python or JSON?
Python uses True/False, JSON uses true/false. If you’re manually building strings, you can accidentally mix these, which breaks strict JSON parsers.
New: pretty printing with custom transforms
Pretty printing is often combined with normalization. Here’s a workflow I use when cleaning up messy JSON:
import json
from datetime import datetime
raw = {
"timestamp": "2026-01-26 15:43:00",
"user_id": "42",
"active": "true",
}
Normalize types
normalized = {
"timestamp": datetime.fromisoformat(raw["timestamp"]).isoformat(),
"userid": int(raw["userid"]),
"active": raw["active"].lower() == "true",
}
print(json.dumps(normalized, indent=2, sort_keys=True))
This keeps the output clean and makes sure the JSON types are correct. I do this before writing files that will be used in tests or audits.
New: alternative approaches you might consider
The built-in json module is usually enough, but sometimes you want different trade-offs.
Option 1: Pretty print with pprint for Python objects
If you’re not committed to JSON output and you just want readability in the terminal, pprint can be faster for quick checks.
from pprint import pprint
pprint({"a": [1, 2, 3], "b": {"x": 1, "y": 2}})
This isn’t JSON, but it’s easy to read during debugging.
Option 2: Use a validation-first approach
If you’re dealing with strict JSON consumers, you might want to validate before pretty printing. That’s especially useful for data pipelines where invalid JSON should fail fast.
Option 3: Use a schema-aware formatter
In data-heavy systems, a schema-aware formatter can reorder or group fields meaningfully. That’s outside the scope of basic pretty printing, but I’ve seen teams use schemas to keep JSON consistent across services.
New: monitoring and safety in long-running systems
Pretty printing might seem like a local concern, but it can have operational impact. Here’s how I keep it safe:
- Guard output size: Don’t log huge payloads even if they’re pretty. Use truncation or sampling.
- Limit depth: If possible, limit how deep you print nested structures.
- Avoid secrets: Pretty output can expose secrets more clearly, so always sanitize sensitive fields.
I once saw a team debug by logging an entire auth payload in a pretty format, which made the secret keys visible in plain text. The readable output actually made the mistake easier to spot—but also more dangerous. Now I always mask fields like token, password, and secret before printing.
New: a masking helper that keeps pretty output safe
Here’s a simple helper to sanitize sensitive keys before printing:
import json
SENSITIVEKEYS = {"password", "token", "secret", "apikey"}
def mask_sensitive(obj):
if isinstance(obj, dict):
return {
k: ("*" if k.lower() in SENSITIVEKEYS else masksensitive(v))
for k, v in obj.items()
}
if isinstance(obj, list):
return [mask_sensitive(v) for v in obj]
return obj
payload = {"user": "ana", "token": "abc123", "profile": {"api_key": "xyz"}}
safe = mask_sensitive(payload)
print(json.dumps(safe, indent=2, ensure_ascii=False))
This is a small step, but it prevents easy leaks in logs and screenshots.
New: working with JSON Lines (NDJSON)
Sometimes data comes as newline-delimited JSON, where each line is a separate JSON object. Pretty printing line-by-line doesn’t make much sense, but you can pretty print a sample or transform it into a standard JSON array for review.
import json
sample = []
with open("events.ndjson", "r", encoding="utf-8") as f:
for i, line in enumerate(f):
if i >= 3:
break
sample.append(json.loads(line))
print(json.dumps(sample, indent=2, ensure_ascii=False))
This gives you a readable snapshot without changing the file format.
New: how I decide indentation in different contexts
People often ask if there’s a “correct” indent. I treat it like typography—choose what matches the medium:
- Logs:
indent=2or no indent - Docs/configs:
indent=2orindent=4 - Snapshots/tests:
indent=2plussort_keys=True - API responses: usually no indent in production, maybe indent behind a debug flag
Consistency matters more than the exact number.
New: troubleshooting checklist
When pretty printing doesn’t work as expected, I walk through this short checklist:
1) Is the data already parsed (dict/list), or is it a JSON string?
2) Are you using the right encoder for datetimes and decimals?
3) Are you accidentally sorting keys and losing a meaningful order?
4) Are you printing too much data for your terminal?
5) Are you sure the output is valid JSON (no NaN, no cycles)?
This catches most issues in minutes.
New: a full mini-utility script I actually use
When I need a quick, reusable command-line tool for prettifying JSON, I keep a tiny script like this:
import json
import sys
from datetime import datetime, date
from decimal import Decimal
class SafeJSONEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, (datetime, date)):
return obj.isoformat()
if isinstance(obj, Decimal):
return str(obj)
return super().default(obj)
Read from stdin and output pretty JSON
try:
data = json.load(sys.stdin)
print(json.dumps(data, indent=2, ensureascii=False, sortkeys=True, cls=SafeJSONEncoder))
except json.JSONDecodeError as e:
print(f"Invalid JSON: {e}", file=sys.stderr)
sys.exit(1)
It’s minimal, but it handles most of the messy cases I run into without extra dependencies.
New: collaborating with teammates and AI assistants
In 2026 workflows, I often use AI to draft transforms or extraction logic, then I pretty print the output before committing it. It’s like a “sanity check” for machine-generated code. I keep a rule: if I wouldn’t show the output to a human reviewer, I probably shouldn’t trust it in production.
I also share pretty-printed samples in code reviews. A clean diff is a huge time saver. If I see compressed JSON in a PR, I’ll often ask for a pretty-printed version before approving the change.
New: keeping pretty output consistent across languages
Many teams have services in multiple languages. If Python pretty prints with 2 spaces and another service uses 4, diffs become messy. I recommend defining a cross-language JSON style guide:
- Indentation: 2 spaces
- Key sorting: yes for tests, no for APIs
- Unicode: preserve
- Float compliance:
allow_nan=Falsefor exports
Once you define this, Python is easy to align with other tools.
New: security and privacy notes I never skip
Pretty output makes data easier to read, and that includes sensitive data. I always ask:
- Are we printing secrets in logs or console output?
- Are we writing PII to disk in a way that’s easy to access?
- Are we exposing hidden fields to users who shouldn’t see them?
Pretty printing should never bypass your masking and redaction rules. If it does, you’ve got a risk.
Key takeaways and next steps
Pretty printing is the easiest way to improve the readability of JSON in Python, and the built-in json module already gives you everything you need. When you add consistent indentation, stable ordering, and safe encoding for real-world types like datetimes and Decimals, your output becomes reliable, test-friendly, and human-readable. That saves time during debugging, reduces mistakes in reviews, and makes logs and exports safer to work with.
If you want to put this into action quickly, I’d start with a small helper like pretty_json(obj) and make it the default in your debugging and test flows. Then add a custom encoder if you touch dates, money, or file paths. Finally, set clear rules for when to avoid pretty printing so you don’t accidentally slow down production systems.
Pretty printing isn’t just formatting—it’s a clarity tool. When you use it consistently, it becomes a low-friction habit that protects you from subtle bugs and painful investigations later.
Expansion Strategy
Add new sections or deepen existing ones with:
- Deeper code examples: More complete, real-world implementations
- Edge cases: What breaks and how to handle it
- Practical scenarios: When to use vs when NOT to use
- Performance considerations: Before/after comparisons (use ranges, not exact numbers)
- Common pitfalls: Mistakes developers make and how to avoid them
- Alternative approaches: Different ways to solve the same problem
If Relevant to Topic
- Modern tooling and AI-assisted workflows (for infrastructure/framework topics)
- Comparison tables for Traditional vs Modern approaches
- Production considerations: deployment, monitoring, scaling


