JSON — JavaScript Object Notation — is the most common format for exchanging data between programs over the internet. Most web APIs speak JSON; most config files are JSON or its cousin YAML.
If you’ve worked with Python dictionaries and lists, JSON will feel completely familiar — it’s basically the same shape on disk.
The mapping between JSON and Python
| JSON | Python |
|---|---|
object { } | dict |
array [ ] | list |
| string | str |
| number | int or float |
true / false | True / False |
null | None |
The Python json module converts between them automatically.
A sample JSON file
Save this as user.json:
{
"name": "Manikandan",
"age": 30,
"skills": ["python", "go", "ml"],
"active": true,
"manager": null
}
Reading JSON
import json
with open("user.json", encoding="utf-8") as f:
data: dict[str, object] = json.load(f)
print(data)
print(data["name"])
print(data["skills"])
{'name': 'Manikandan', 'age': 30, 'skills': ['python', 'go', 'ml'], 'active': True, 'manager': None}
Manikandan
['python', 'go', 'ml']
json.load(f) reads from a file. json.loads(text) (with an s) reads from a string — useful when the JSON came from an API:
text: str = '{"name": "Alice", "age": 25}'
data: dict[str, object] = json.loads(text)
print(data["name"])
Writing JSON
data: dict[str, object] = {
"name": "Manikandan",
"age": 30,
"skills": ["python", "go", "ml"],
"active": True,
"manager": None,
}
with open("output.json", "w", encoding="utf-8") as f:
json.dump(data, f, indent=2)
{
"name": "Manikandan",
"age": 30,
"skills": [
"python",
"go",
"ml"
],
"active": true,
"manager": null
}
indent=2 pretty-prints with 2-space indentation. Without it, the output is one long line — fine for machines, painful for humans.
json.dumps(data) (with an s) returns a JSON string instead of writing to a file:
text: str = json.dumps(data, indent=2)
print(text)
What you can and can’t serialise
JSON only supports the types in the table above. Things that don’t work:
set— not in JSON. Convert to alistfirst.tuple— comes back as alistafter a round trip.datetime, custom classes — need extra handling.NaN,Infinity— work in Python but aren’t strictly valid JSON.
import json
from datetime import datetime
data = {"created_at": datetime.now()}
json.dumps(data)
# TypeError: Object of type datetime is not JSON serializable
The fix is usually to convert before serialising — data["created_at"] = data["created_at"].isoformat() — or pass a custom default function. We’ll keep things simple here.
Common arguments
json.dump(data, f, indent=2) # pretty-printed
json.dump(data, f, sort_keys=True) # keys in alphabetical order
json.dump(data, f, ensure_ascii=False) # keep non-ASCII characters as-is
json.dump(data, f, separators=(",", ":")) # most compact form (no spaces)
For human-readable output (configs, debug dumps), use indent=2, sort_keys=True, ensure_ascii=False. For maximum compactness (sending over the network), use separators=(",", ":").
Handling JSON errors
json.load raises json.JSONDecodeError if the file isn’t valid JSON:
try:
with open("bad.json", encoding="utf-8") as f:
data = json.load(f)
except json.JSONDecodeError as e:
print(f"Bad JSON: {e}")
The error message tells you which line and column the parser tripped on — useful when debugging hand-edited config files.
A complete example — a small notes app
A program that reads notes from a JSON file, appends one, and writes it back:
import json
from pathlib import Path
def load_notes(path: Path) -> list[str]:
if not path.exists():
return []
with open(path, encoding="utf-8") as f:
return json.load(f)
def save_notes(path: Path, notes: list[str]) -> None:
with open(path, "w", encoding="utf-8") as f:
json.dump(notes, f, indent=2, ensure_ascii=False)
path: Path = Path("notes.json")
notes: list[str] = load_notes(path)
new_note: str = input("New note: ")
notes.append(new_note)
save_notes(path, notes)
print(f"Total notes: {len(notes)}")
Run it a few times. Each run loads the existing list, appends one, saves it back. The notes.json file grows naturally.
JSON vs other formats
- JSON — universal, widely supported, no comments allowed.
- YAML — looks like JSON but with comments and cleaner syntax. Popular for configs (Docker, Kubernetes, GitHub Actions). Needs a third-party package (
pyyaml). - TOML — the format Python itself uses for
pyproject.toml. Comes built-in astomllib(read-only) in Python 3.11+.
For exchanging data with anything outside Python: JSON. Inside a Python-only project, TOML or JSON for config.
What’s next
Last lesson of the section — pathlib, the modern way to work with file paths.