🐍 Python Course · Stage 3 · Lesson 31 of 89 · json — Data Exchange
Course Overview →

🔵 Read Beginner and Intermediate tabs. The Expert tab has the internals — worth reading once you finish Stage 3.

← pathlib — File Paths re — Regex →
thecodex.expert · The Codex Family of Knowledge
Python stdlib

Python json — JSON Encoder and Decoder module

Python dicts to JSON and back — the universal data exchange format of the web.

Python 3.13 Standard Library Last verified:
Canonical Definition

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.

🟩 Beginner

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.

⏱ 25 min 📄 2 sections 🔶 Prerequisite: File I/O
One sentence

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

Pythonjson_encode.py
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

Pythonjson_decode.py
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 dict
💡

The 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

Pythonjson_custom.py
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

Continue to re — Regular Expressions →

🔵 Intermediate

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.

⏱ 20 min 📄 1 section 🔶 Prerequisite: Beginner tab
📎

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.

Pythonjson_advanced.py
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'>
Commonly confused
JSON null maps to Python 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.
JSON has no datetime type. Datetimes must be serialised as strings (ISO 8601: "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

Continue to re — Regular Expressions →

🔴 Expert

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.

⏱ 20 min 📄 2 sections 🔶 Prerequisite: After Stage 2
📎

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

Continue to re — Regular Expressions →

Sources

1
Python Standard Library — json. docs.python.org/3/library/json.html.
2
RFC 8259 — The JavaScript Object Notation (JSON) Data Interchange Format. datatracker.ietf.org/doc/html/rfc8259.
3
ECMA-404 — The JSON Data Interchange Standard. ecma-international.org.
Source confidence: High Last verified: Primary source: docs.python.org/3/library/json.html

Sources

1
Python Standard Library — json. docs.python.org/3/library/json.html.
2
RFC 8259 — JSON Data Interchange Format. datatracker.ietf.org/doc/html/rfc8259.
3
ECMA-404 — The JSON Standard. ecma-international.org.