Formatting JSON Output in Python: Complete JSON Pretty Printing and Output Customization Guide

Formatting JSON output in python

I used to think formatting JSON was purely cosmetic — something you’d only care about if you wanted your output to “look nice.” That opinion changed the first time I had to diff two large JSON API responses to find a single changed field, and the raw, single-line output made that nearly impossible. Formatting JSON output well is genuinely a practical skill, and Python’s json module gives me a surprising amount of control over it once I dug into the details. Here’s everything I’ve learned about shaping JSON output the way I actually need it.

The Default Output: Compact and Unforgiving

By default, json.dumps() produces the most compact representation it can, with no line breaks and minimal spacing.

import json

data = {"name": "Alice", "age": 30, "skills": ["Python", "SQL"]}
print(json.dumps(data))

Output:

{"name": "Alice", "age": 30, "skills": ["Python", "SQL"]}

This is efficient for machine-to-machine transmission — smaller payloads mean less bandwidth and faster parsing — but it’s genuinely painful to read once the data has any real depth or size.

Pretty Printing with indent

The single most useful formatting parameter is indent. Passing an integer tells json.dumps() to insert that many spaces of indentation per nesting level, along with newlines.

import json

data = {
    "name": "Alice",
    "age": 30,
    "address": {
        "city": "Austin",
        "state": "TX"
    },
    "skills": ["Python", "SQL", "Docker"]
}

print(json.dumps(data, indent=4))

Output:

{
    "name": "Alice",
    "age": 30,
    "address": {
        "city": "Austin",
        "state": "TX"
    },
    "skills": [
        "Python",
        "SQL",
        "Docker"
    ]
}

I typically use indent=2 for web-facing debug output (it’s the convention most JSON tools and viewers default to) and indent=4 when I want something closer to standard Python code indentation for local config files.

Controlling Separators

Right below indent in usefulness is the separators parameter, which controls the strings used between items and between keys and values.

import json

data = {"a": 1, "b": 2, "c": 3}

# Default (with indent, trailing whitespace is stripped by default sensibly)
print(json.dumps(data, indent=2))

# Most compact possible output - no unnecessary spaces at all
print(json.dumps(data, separators=(',', ':')))

Output:

{
  "a": 1,
  "b": 2,
  "c": 3
}
{"a":1,"b":2,"c":3}

That second line, using separators=(',', ':'), is the tightest possible JSON representation — no spaces anywhere. I reach for this specifically when I’m optimizing payload size for high-throughput APIs or minimizing storage in a database column, where every byte across millions of records adds up.

By default, when indent is None (the default), json.dumps() actually uses (', ', ': ') as separators — note the spaces after the comma and colon. When you supply indent, Python automatically switches the default item separator to just ',' (dropping the trailing space, since the newline and indentation already provide visual separation). Understanding this default-switching behavior saved me some confusion early on when my “compact” output with indent=0 still had spaces I wasn’t expecting from certain separator combinations.

Sorting Keys for Consistent, Comparable Output

When I need to diff two JSON outputs, or generate consistent hashes of JSON content, key order matters enormously. sort_keys=True forces alphabetical ordering of all dictionary keys, recursively, regardless of the original insertion order.

import json

data = {"zebra": 1, "apple": 2, "mango": 3}
print(json.dumps(data, sort_keys=True, indent=2))

Output:

{
  "apple": 2,
  "mango": 3,
  "zebra": 1
}

I use this constantly in test suites — comparing expected vs. actual JSON output becomes reliable rather than order-dependent when both sides are sorted the same way.

Formatting Numbers

JSON doesn’t distinguish between int and float the way Python does at the language level, but Python’s encoder still needs rules for how to render floats as text, which occasionally surprises people.

import json

data = {"price": 19.99, "quantity": 5, "discount": 0.1}
print(json.dumps(data))

Output:

{"price": 19.99, "quantity": 5, "discount": 0.1}

Floats are rendered using Python’s repr() for floats internally, which uses the shortest string that round-trips back to the exact same float value. Special float values like NaN, Infinity, and -Infinity are, by default, serialized using non-standard JSON extensions (NaN, Infinity, -Infinity) that many strict JSON parsers in other languages will reject.

import json

data = {"value": float('nan')}
print(json.dumps(data))  # {"value": NaN}  <- technically invalid JSON per the spec

I set allow_nan=False when I need strictly spec-compliant JSON output for a cross-language system, which raises a ValueError instead of silently producing non-standard output:

try:
    json.dumps({"value": float('nan')}, allow_nan=False)
except ValueError as e:
    print(f"Error: {e}")

Custom Item Separators for Different Formatting Needs

Sometimes I want indentation for readability but a tighter separator than the default. This combination is useful for a “medium density” output style:

import json

data = {"name": "Bob", "role": "engineer", "active": True}
print(json.dumps(data, indent=2, separators=(',', ': ')))

Output:

{
  "name": "Bob",
  "role": "engineer",
  "active": true
}

Formatting Output for File Writing

When writing formatted JSON to disk — configuration files being the most common case for me — I combine indent with a trailing newline for cleanliness, since json.dump() doesn’t add one automatically, and many text editors/linters expect files to end with a newline.

import json

config = {"debug": False, "max_connections": 10, "timeout": 30}

with open('config.json', 'w') as f:
    json.dump(config, f, indent=2)
    f.write('\n')

Re-Formatting Existing JSON (Pretty-Printing a Minified File)

A task I do surprisingly often: taking a minified JSON blob (from an API log, a downloaded file, or pasted text) and reformatting it for readability. The trick is that I have to parse it back into a Python object first, then re-dump it with formatting — json has no direct “reformat this string” function.

import json

minified = '{"user":"alice","roles":["admin","editor"],"active":true}'

parsed = json.loads(minified)
pretty = json.dumps(parsed, indent=2, sort_keys=True)
print(pretty)

Output:

{
  "active": true,
  "roles": [
    "admin",
    "editor"
  ],
  "user": "alice"
}

This is effectively how command-line tools like jq or python -m json.tool work internally — parse, then re-serialize with formatting options.

Using the json.tool Command-Line Module

Worth mentioning since it’s directly related and built into Python: I use python -m json.tool from the terminal all the time to quickly pretty-print a JSON file without writing any code at all.

python -m json.tool messy_data.json
python -m json.tool messy_data.json formatted_output.json

This uses the exact same json module internals under the hood, just exposed as a CLI utility — it’s essentially json.load() followed by json.dump(..., indent=4).

Performance Considerations of Formatting Choices

Adding indent isn’t free — the encoder has to track nesting depth and insert whitespace characters throughout the recursive descent, which adds measurable overhead on very large structures compared to compact output.

import json
import time

large_data = [{"id": i, "name": f"item_{i}", "active": i % 2 == 0} for i in range(200000)]

start = time.perf_counter()
json.dumps(large_data)
print(f"Compact: {time.perf_counter() - start:.4f}s")

start = time.perf_counter()
json.dumps(large_data, indent=2)
print(f"Indented: {time.perf_counter() - start:.4f}s")

In my experience, indented output takes noticeably longer and produces a meaningfully larger string (sometimes 30-50% larger in byte size) due to all the added whitespace. For anything served over a network at scale — API responses, message queue payloads — I default to compact output and reserve pretty-printing for debug logs, development environments, or files a human will actually open.

Common Mistakes I’ve Made

  • Sending pretty-printed JSON over a production API without realizing the extra whitespace was inflating response sizes and slowing things down at scale.
  • Assuming indent=0 means “no formatting” — it actually still adds newlines between elements, just with zero spaces of indentation, which surprised me the first time I used it.
  • Forgetting sort_keys=True when comparing JSON snapshots in tests, leading to flaky test failures caused purely by dict ordering rather than actual content differences.
  • Not setting allow_nan=False when JSON needs to be consumed by a strict, spec-compliant parser in another language, and having NaN values silently break the receiving system.

FAQs

What’s the difference between indent=0 and indent=None? indent=None (the default) produces fully compact single-line output. indent=0 produces multi-line output with newlines between elements but zero spaces of actual indentation — a subtle but real difference.

How do I make JSON output as small as possible? Use separators=(',', ':') with indent=None — this removes every unnecessary space, minimizing the byte size of the string.

Can I control the order of keys without sorting alphabetically? Yes — since Python 3.7, dicts preserve insertion order, so if you don’t pass sort_keys=True, the JSON output preserves whatever order the keys were inserted into the dict.

Why does my pretty-printed JSON look different from what a browser dev tools panel shows? Different tools use different default indentation widths and key-sorting behavior. The underlying data is identical; only the formatting choices differ.

Is there a way to format JSON without writing custom Python code? Yes — python -m json.tool from the command line handles pretty-printing without any script needed, using the same module under the hood.

Summary

Formatting JSON output in Python is about far more than aesthetics — indent, separators, and sort_keys directly affect payload size, comparison reliability, and human readability, and choosing the right combination depends entirely on whether the output is headed for a network wire, a config file, or a developer’s terminal. Once I understood the trade-offs between compact and pretty-printed output, and how Python’s default separators quietly change based on whether indent is set, formatting stopped being guesswork and became a deliberate choice I make for every piece of JSON I produce.

References

Total
0
Shares

Leave a Reply

Previous Post
Retrieving data from a file in python

Retrieving Data from a File in Python: Complete File Reading and Data Extraction Implementation Guide

Next Post
JSON encoding custom objects in python

JSON Encoding Custom Objects in Python: Complete Custom Serialization and Deserialization Guide

Related Posts