JSON Formatter Python β€” json.dumps() Guide

Β·Backend DeveloperΒ·Reviewed byDmitri VolkovΒ·Published

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.

Key Takeaways
  • β†’json.dumps(data, indent=4) is built into Python's stdlib since 2.6 β€” no install needed.
  • β†’Pass ensure_ascii=False any time your data contains accented letters, CJK characters, or emoji.
  • β†’For datetime, UUID, or custom classes use the default= parameter or subclass json.JSONEncoder.
  • β†’separators=(',', ':') strips all whitespace β€” use for network transfer or URL embedding.
  • β†’orjson is 5–10Γ— faster than the stdlib and natively handles datetime and uuid.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 ijson instead of json.load() to avoid MemoryError.

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.

Before Β· json
After Β· json
{"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.

Python 3.8+ β€” minimal example
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):

Python 3.8+ β€” with sort_keys and ensure_ascii
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"
# }
Note: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.

Parameter
Type
Default
Description
obj
any
β€”
Python object to serialize to a JSON-formatted string.
indent
int | str | None
None
Spaces per indent level. None = compact single-line, 0 = newlines only, 4 = standard.
sort_keys
bool
False
Sort dictionary keys alphabetically across all nested levels.
ensure_ascii
bool
True
Escape non-ASCII chars to \uXXXX. Set False to preserve Unicode characters as-is.
separators
tuple | None
None
(item_sep, key_sep) pair. Use (",", ":") for the most compact output without spaces.
default
callable | None
None
Called for types not serializable by default. Raise TypeError to reject the value.
allow_nan
bool
True
Serialize float("nan") and float("inf") as JS literals. Set False to raise ValueError.

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.

Python 3.8+
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"}
Note:When you pass 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.

Python 3.8+ β€” TypeError without custom handling
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 TypeError

Approach 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.

Python 3.8+
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.

Python 3.8+
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 above
Note:Always call super().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.

Python 3.8+
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:00
Note:The object_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.

Python 3.8+
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
# }
Warning:Never send 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

Python 3.8+
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

Python 3.8+ (requires: pip install requests)
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")
Note: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.

bash
# 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
Note:Python 3.9 added --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.

bash β€” install
pip install orjson
Python 3.8+
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.

bash β€” install
pip install rich
Python 3.8+
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))
Warning: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.

bash β€” install
pip install simplejson
Python 3.8+
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
# }
Note:For pure performance, 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.

bash β€” install
pip install ijson
Python 3.8+
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 usage
Note:Switch from json.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.

Python 3.8+
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.

❌ Using print(data) instead of json.dumps()

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.

Before Β· Python
After Β· Python
data = {"active": True, "count": None}
print(data)
# {'active': True, 'count': None}
print(json.dumps(data, indent=4))
# {
#     "active": true,
#     "count": null
# }
❌ Forgetting ensure_ascii=False with non-ASCII text

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.

Before Β· Python
After Β· Python
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"}
❌ Using json.dumps() to write to a file

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.

Before Β· Python
After Β· Python
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)
❌ Pretty-printing with pprint and expecting valid JSON

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.

Before Β· Python
After Β· Python
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.

Method
Output
Valid JSON
Speed
Non-ASCII
Custom Types
Install
json.dumps(indent=4)
String
βœ…
Standard
ensure_ascii=False
default= / JSONEncoder
Built-in
json.dump(f, indent=4)
File
βœ…
Standard
ensure_ascii=False
default= / JSONEncoder
Built-in
pprint.pprint()
Python repr
❌
Standard
Native
βœ… (any repr)
Built-in
orjson.dumps(OPT_INDENT_2)
Bytes
βœ…
5–10Γ— faster
Native
datetime, UUID, numpy
pip install orjson
python -m json.tool
CLI stdout
βœ…
Standard
βœ…
❌
Built-in
simplejson.dumps()
String
βœ…
~1.5Γ— faster
ensure_ascii=False
Decimal natively
pip install simplejson
rich.print_json()
Terminal only
βœ… (input)
Standard
βœ…
❌
pip install rich

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.

python
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.

python
# 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.

python
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.

python
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.

python
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 JSON

How 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.

python
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.

Also available in:GoJavaScriptBash
MS
Maria SantosBackend Developer

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.

DV
Dmitri VolkovTechnical Reviewer

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.