{ }jsonkitOpen

Working with JSON in Python: the json module

6 min read · updated 2026-07-12

Python's standard library includes a json module, so working with JSON in Python needs no extra package. It handles both directions: JSON text into Python objects, and Python objects back into JSON text.

This guide covers the four core functions, how Python types map to JSON, pretty-printing options, and the parts that cause trouble in production: non-ASCII output, serializing datetime and Decimal, and streaming large files as NDJSON. Every section has runnable code. If you also work in the browser, Working with JSON in JavaScript covers the same ideas with parse and stringify.

The four core functions: load, loads, dump, dumps

The json module has two pairs of functions. The s suffix means "string". loads and dumps work with data in memory. load and dump work with file-like objects. Once this is clear, the rest of the module follows.

FunctionDirectionWorks with
json.loads(s)JSON to Pythona str, bytes, or bytearray
json.dumps(obj)Python to JSONreturns a str
json.load(fp)JSON to Pythona file object
json.dump(obj, fp)Python to JSONwrites to a file object
Round-tripping between strings and objectspython
import json

# Parse JSON text into a Python object
data = json.loads('{"name": "Ada", "age": 36, "active": true}')
print(data["name"])   # Ada
print(type(data))     # <class 'dict'>

# Serialize a Python object back to JSON text
payload = {"name": "Ada", "roles": ["admin", "dev"], "active": True}
text = json.dumps(payload)
print(text)           # {"name": "Ada", "roles": ["admin", "dev"], "active": true}

Use the file versions when you read or write a file directly. You skip building the whole document as an intermediate string in your own code.

Reading and writing filespython
import json

# Read a JSON file
with open("config.json", encoding="utf-8") as f:
    config = json.load(f)

# Write a JSON file
with open("out.json", "w", encoding="utf-8") as f:
    json.dump(config, f)

Parsing JSON from a string, file or API response

In real code, JSON comes from three main places, and each one ends up at json.loads or json.load.

From an API response

If you use the requests library, the response object has a .json() method that parses the body for you. Without it, decode and parse the text yourself.

Parsing an HTTP responsepython
import json
import urllib.request

with urllib.request.urlopen("https://api.example.com/users/1") as resp:
    body = resp.read().decode("utf-8")

user = json.loads(body)
print(user["email"])

# With requests, this is simply:
# user = requests.get(url).json()

Always wrap parsing of untrusted input in try/except. Malformed JSON raises json.JSONDecodeError, a subclass of ValueError. The exception has .msg, .lineno, .colno and .pos, so you can report exactly where the problem is.

examplepython
import json

try:
    data = json.loads(raw_text)
except json.JSONDecodeError as e:
    print(f"Invalid JSON at line {e.lineno}, column {e.colno}: {e.msg}")

How Python types map to JSON

Serialization translates types. The mapping explains why True becomes true and why a tuple comes back as a list. JSON has only six value types, so Python's larger type system gets flattened on the way out.

PythonJSON
dictobject
list, tuplearray
strstring
int, floatnumber
True / Falsetrue / false
Nonenull

Decoding runs the mapping in reverse. JSON objects become dict, arrays become list, true/false become True/False, and null becomes None. The one to remember is the tuple. It serializes to a JSON array, and no JSON type decodes to a tuple, so it comes back as a list. For more on JSON's value types, see JSON Data Types Explained.

Pretty-printing with indent and sort_keys

By default json.dumps writes everything on one line, with , and : as separators. For readable output, pass indent. For stable output that diffs cleanly, add sort_keys=True.

Readable, deterministic outputpython
import json

data = {"name": "Ada", "roles": ["admin", "dev"], "active": True}

print(json.dumps(data, indent=2, sort_keys=True))
# {
#   "active": true,
#   "name": "Ada",
#   "roles": [
#     "admin",
#     "dev"
#   ]
# }

Use sort_keys=True whenever JSON output is committed to version control or compared across runs. With sorted keys, the same data always produces identical bytes, so diffs show real changes instead of reordering noise.

To shrink JSON for transport, pass a tight separators tuple. It removes the spaces dumps adds by default:

examplepython
import json

json.dumps(data, separators=(",", ":"))
# {"name":"Ada","roles":["admin","dev"],"active":true}
JSON FormatterTo inspect or reformat JSON without running Python, paste it into the JSON Formatter. It pretty-prints and validates the JSON in your browser.→

For when compact or indented output matters, see How to Format and Beautify JSON and How to Minify JSON.

ensure_ascii and Unicode output

This one surprises most people. ensure_ascii defaults to True, so every non-ASCII character is escaped as a \uXXXX sequence. The result is valid JSON and safe for any transport, but non-Latin text becomes unreadable.

Keeping Unicode readablepython
import json

data = {"city": "Zürich", "emoji": "🚀"}

print(json.dumps(data))
# {"city": "Zürich", "emoji": "🚀"}

print(json.dumps(data, ensure_ascii=False))
# {"city": "Zürich", "emoji": "🚀"}

Set ensure_ascii=False for readable output, but then you must control the encoding yourself. When writing to a file, open it with encoding="utf-8". Otherwise Python may use a platform encoding that cannot represent the characters and raise a UnicodeEncodeError.

examplepython
import json

with open("out.json", "w", encoding="utf-8") as f:
    json.dump(data, f, ensure_ascii=False, indent=2)

Custom encoders: datetime, Decimal and default=

The module can serialize the built-in JSON types and nothing else. Give it a datetime, Decimal, set, UUID or your own class and it raises TypeError: Object of type ... is not JSON serializable. The fix is the default parameter, a function that json calls for any object it cannot handle. Return something serializable, or raise TypeError to reject it.

A reusable default functionpython
import json
from datetime import datetime, date
from decimal import Decimal
from uuid import UUID

def json_default(obj):
    if isinstance(obj, (datetime, date)):
        return obj.isoformat()
    if isinstance(obj, Decimal):
        return float(obj)      # or str(obj) to preserve exact precision
    if isinstance(obj, UUID):
        return str(obj)
    if isinstance(obj, set):
        return sorted(obj)
    raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")

record = {
    "id": UUID("12345678-1234-5678-1234-567812345678"),
    "created": datetime(2026, 7, 12, 9, 30),
    "price": Decimal("19.99"),
    "tags": {"python", "json"},
}

print(json.dumps(record, default=json_default, indent=2))

datetime has no JSON type

JSON has no date type, so a datetime must become a string or a number. ISO 8601 from .isoformat() is the standard choice. It sorts correctly, is unambiguous, and every language can parse it. The conversion only goes one way. When you read the JSON back, "2026-07-12T09:30:00" comes back as a plain str, and you convert it with datetime.fromisoformat() yourself.

Decimal loses precision when cast to float

Converting Decimal to float brings back the binary floating-point rounding that Decimal was meant to avoid. For money and other exact values, serialize with str(obj) instead. Read it back with json.loads(text, parse_float=Decimal) so numbers become Decimal rather than float.

examplepython
import json
from decimal import Decimal

text = '{"total": 19.99, "tax": 1.60}'
exact = json.loads(text, parse_float=Decimal)
print(exact["total"])          # Decimal('19.99') exact decimal, no binary rounding
print(type(exact["total"]))    # <class 'decimal.Decimal'>

Reading and writing NDJSON line by line

NDJSON (newline-delimited JSON) puts one complete JSON object on each line. Logs, data exports and streaming APIs use it because you can process one record at a time without loading a multi-gigabyte file into memory. The file as a whole is not one JSON document. Each line is. So you call json.loads on each line, not json.load on the file.

Streaming an NDJSON filepython
import json

def read_ndjson(path):
    with open(path, encoding="utf-8") as f:
        for line in f:
            line = line.strip()
            if not line:          # skip blank lines
                continue
            yield json.loads(line)

for event in read_ndjson("events.ndjson"):
    print(event["id"], event["type"])

read_ndjson is a generator, so memory use stays flat however large the file is. Only one parsed record exists at a time. Writing NDJSON works the same way: dump each object with dumps and add a newline. Keep ensure_ascii and separators the same for every line.

examplepython
import json

with open("events.ndjson", "w", encoding="utf-8") as f:
    for event in events:
        f.write(json.dumps(event, ensure_ascii=False))
        f.write("\n")

NDJSON is one of several JSON variants. For how it compares to JSON5 and JSONC, see JSON5, JSONC, and NDJSON Explained.

Frequently asked questions

What is the difference between json.load and json.loads?

json.loads (the 's' is for string) parses JSON from a string or bytes already in memory. json.load reads JSON from a file-like object. Output works the same way: dump writes to a file, and dumps returns a string.

How do I pretty-print JSON in Python?

Pass indent to json.dumps, for example json.dumps(data, indent=2). Add sort_keys=True for a fixed key order, and ensure_ascii=False if you want readable non-ASCII characters instead of \u escapes.

Why does json.dumps output \u escapes for accented or non-English characters?

ensure_ascii defaults to True, which escapes every non-ASCII character as a \uXXXX sequence. Pass ensure_ascii=False to keep the original characters, and open any output file with encoding="utf-8".

How do I serialize a datetime to JSON in Python?

JSON has no date type, so convert it to a string. Pass a default function to json.dumps that returns obj.isoformat() for datetime objects. When you read the JSON back, the value is a string that you parse with datetime.fromisoformat().

How do I parse a large JSON file without loading it all into memory?

Use NDJSON (one JSON object per line) and read the file line by line, calling json.loads on each stripped line. A generator keeps memory flat whatever the file size. A standard single-document JSON file still has to be read in full with the built-in module.

Does Python keep dictionary order when serializing to JSON?

Yes. Since Python 3.7, dicts keep insertion order, and json.dumps writes keys in that order. Pass sort_keys=True if you want alphabetical order instead, for stable output that diffs cleanly.