The Python json module encodes Python objects (dict, list, str, int, float, bool, None) to JSON strings and decodes JSON strings back to Python objects, following the RFC 8259 JSON specification.
Reading and writing JSON data
What you'll learn: The four json functions you'll use constantly — load, loads, dump, dumps — and the type mapping between Python and JSON.
How to read this tab: Open the REPL and try: import json; data = json.loads('{"name": "Priya", "age": 30}'); print(data); print(type(data)). Then try json.dumps(data, indent=2). Feel the load/loads and dump/dumps distinction.
The json module converts between Python objects (dicts, lists, strings, numbers, booleans, None) and JSON strings — the universal data format of the web.
load vs loads — this trips everyone up. The "s" stands for "string". json.load(f) reads from a file object. json.loads(s) parses a JSON string. json.dump(data, f) writes to a file. json.dumps(data) returns a JSON string. No "s" = file. With "s" = string. Write this on a sticky note.
Encoding Python → JSON
import json
data = {
"name": "Priya Sharma",
"age": 28,
"city": "Mumbai",
"skills": ["Python", "SQL", "pandas"],
"employed": True,
"score": 9.5,
"notes": None
}
# json.dumps() — Python object to JSON string
compact = json.dumps(data)
print(compact)
# {"name": "Priya Sharma", "age": 28, ...}
# Pretty-printed with indentation
pretty = json.dumps(data, indent=2, ensure_ascii=False)
print(pretty)
# Write directly to a file
with open("data.json", "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
# ensure_ascii=False: preserve non-ASCII (e.g. ₹, हिंदी)JSON objects become Python dicts — always. JSON arrays become lists. JSON strings become str. JSON numbers become int or float. JSON null becomes None. JSON true/false become True/False. Knowing this mapping means you can predict exactly what json.loads() will return from any JSON.
Decoding JSON → Python
import json
json_string = '''
{
"name": "Priya",
"age": 28,
"skills": ["Python", "SQL"],
"active": true
}
'''
# json.loads() — JSON string to Python object
data = json.loads(json_string)
print(data["name"]) # Priya
print(type(data)) # <class 'dict'>
print(type(data["age"])) # <class 'int'>
# Read from a file
with open("data.json", "r", encoding="utf-8") as f:
loaded = json.load(f)
# JSON type mapping
# JSON null → Python None
# JSON true → Python True
# JSON false → Python False
# JSON number → Python int or float
# JSON string → Python str
# JSON array → Python list
# JSON object → Python dictThe simplest approach for non-serialisable types: json.dumps(data, default=str). This converts anything json doesn't know how to handle into a string. It's not precise, but it's fast. For production code, write a proper default function that handles each type explicitly.
Custom serialisation
import json
from datetime import datetime, date
from decimal import Decimal
# json.dumps raises TypeError for non-JSON-serialisable types
# datetime, Decimal, custom objects — need custom encoder
class PrecisionEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime):
return obj.isoformat() # "2026-06-01T14:30:00"
if isinstance(obj, date):
return obj.isoformat() # "2026-06-01"
if isinstance(obj, Decimal):
return float(obj) # precision loss — be careful
return super().default(obj) # raises TypeError for unknown types
data = {
"timestamp": datetime.now(),
"price": Decimal("99.99"),
}
print(json.dumps(data, cls=PrecisionEncoder, indent=2))
# Simpler: use default= parameter for a single conversion function
def json_default(obj):
if isinstance(obj, (datetime, date)):
return obj.isoformat()
raise TypeError(f"Object of type {type(obj)} is not JSON serialisable")
json.dumps(data, default=json_default)✅ Beginner tab complete — check your understanding
- I know: json.load() reads from a file, json.loads() parses a string
- I know: json.dump() writes to a file, json.dumps() returns a string
- I know the Python ↔ JSON type mapping (dict↔object, list↔array, None↔null)
- I can pretty-print JSON with indent=2
Custom serialisation and error handling
What you'll learn: How to serialise types JSON doesn't know about (dates, custom objects) using the default= parameter. How to handle JSONDecodeError gracefully.
How to read this tab: Try serialising a datetime.date object to JSON without any customisation — you'll get a TypeError. Then use the default= parameter to fix it.
For high-throughput JSON: orjson (pip install orjson) is typically 5-10x faster than the standard library json module and supports more types natively (datetime, numpy arrays, dataclasses). Worth knowing about for data-intensive applications.
Performance and alternatives
Python's built-in json module is written in Python (with a C accelerator). For high-performance JSON, consider orjson (Rust-based, 3–10× faster, handles datetime/numpy natively) or ujson. orjson returns bytes, not str. For config files, consider tomllib (Python 3.11+ stdlib for TOML) or PyYAML for YAML.
import json
# Sorting keys for deterministic output
data = {"b": 2, "a": 1, "c": 3}
print(json.dumps(data, sort_keys=True)) # {"a": 1, "b": 2, "c": 3}
# Separators — compact output without spaces
compact = json.dumps(data, separators=(",", ":"))
print(compact) # {"b":2,"a":1,"c":3}
# object_hook — convert dict to custom class on decode
class Config:
def __init__(self, d):
self.__dict__ = d
cfg = json.loads('{"host":"localhost","port":5432}',
object_hook=Config)
print(cfg.host) # localhost
print(cfg.port) # 5432
# parse_float / parse_int — control number parsing
from decimal import Decimal
precise = json.loads('{"price": 9.99}',
parse_float=Decimal)
print(precise["price"]) # Decimal('9.99') — no float precision loss
print(type(precise["price"])) # <class 'decimal.Decimal'>None, not the string "null". When you decode {"value": null}, you get {"value": None} in Python. When you encode {"value": None}, you get {"value": null} in JSON. They are equivalent."2026-06-01T14:30:00" is the convention). The json module does not do this automatically — you need a custom encoder or a library like orjson that handles it.json.dumps() returns a string; json.dump() writes to a file. The s variants (dumps, loads) work with strings. The non-s variants (dump, load) work with file objects. The same convention applies across the stdlib: pickle.dumps/pickle.dump, etc.✅ Intermediate tab complete — check your understanding
- I can use json.dumps(data, default=str) to handle non-serialisable types simply
- I can write a custom default= function for precise control over serialisation
- I handle json.JSONDecodeError when loading data from external sources
Performance, RFC 8259, and alternatives
What you'll learn: The json module's performance characteristics. The JSON specification (RFC 8259) — what is and isn't valid JSON. Faster alternatives like orjson and ujson for high-throughput scenarios.
How to read this tab: Read when processing large JSON files where performance matters.
The JSON spec forbids NaN and Infinity. Python's json module allows them by default (allow_nan=True). If you're exchanging JSON with other systems, pass allow_nan=False to get strict RFC compliance. Also: JSON keys must be strings — Python dicts with integer keys will have their keys converted to strings on serialisation.
RFC 8259 and JSON specification
JSON is specified by RFC 8259 (December 2017, superseding RFC 7159 and RFC 4627). RFC 8259 specifies: JSON is text encoded as UTF-8, UTF-16, or UTF-32 — Python's json module uses UTF-8 by default. A JSON value is one of: object, array, number, string, true, false, null. Numbers have no size limit in the spec; Python's parser maps integers to int (arbitrary precision) and floats to IEEE 754 double. The ensure_ascii=True default escapes all non-ASCII characters as \uXXXX; set ensure_ascii=False to emit raw Unicode.
✅ Expert tab complete
- I know what RFC 8259 says about JSON keys (must be strings), NaN/Infinity (not valid JSON), and encoding
- I know orjson is ~5-10x faster than the standard json module for large payloads