JSON and Python Speak the Same Shapes
JSON is the default format for APIs, config files, and data interchange on the web. Luckily for Python programmers, a JSON object maps directly onto a Python dict, a JSON array onto a list, a JSON string onto a str. That close alignment is why reading and writing JSON in Python is a two-line operation.
The standard library module json handles both directions.
Parsing a JSON String
json.loads(text) — "load string" — takes a JSON-formatted string and returns the Python object it represents:
The result is a plain dict. You access its keys like any dict — no special JSON wrapper, no .parse() method on the result.
If the string isn't valid JSON, json.loads raises json.JSONDecodeError. The error message includes the line and column of the problem, which is usually enough to spot a missing comma or an unescaped quote.
Writing JSON as a String
json.dumps(data) — "dump string" — does the opposite: it takes a Python object and returns a JSON string:
Python's True, False, and None get translated to JSON's true, false, and null automatically. Numbers and strings pass through unchanged. Lists become arrays, dicts become objects.
Pretty Printing
The default output of json.dumps is compact — fine for network transport, hard for humans. Pass indent=2 for something more readable:
A few other dumps options worth knowing:
sort_keys=True— sort object keys alphabetically. Useful for deterministic output (config files, test fixtures, diffs).ensure_ascii=False— write non-ASCII characters (é, ü, 中) as themselves instead of\uescapes. Usually the right call for UTF-8 files.separators=(",", ":")— tightest possible output. Combined withensure_ascii=False, gives the most compact UTF-8 JSON you can produce.
Reading JSON From a File
json.load(file) — no s — reads directly from a file object. The common pattern pairs it with with open:
import json
with open("config.json") as f:
config = json.load(f)
print(config["version"])
No need to read the whole file into a string first; json.load streams through the file object.
Writing JSON to a File
json.dump(data, file) is the file-writing counterpart:
import json
data = {"created": "2026-01-01", "items": [1, 2, 3]}
with open("state.json", "w") as f:
json.dump(data, f, indent=2)
The indent option works here too. Open the resulting file and you'll see a nicely formatted JSON document.
Reading a JSON API Response
Most HTTP libraries hand you bytes or text; you call json.loads to turn them into usable data:
import json
import urllib.request
with urllib.request.urlopen("https://api.example.com/users/1") as response:
text = response.read().decode("utf-8")
user = json.loads(text)
print(user["name"])
The requests library (covered separately) skips a step — it has a .json() method on the response that calls json.loads for you.
What JSON Can and Can't Represent
JSON's type system is narrower than Python's. The mapping in both directions:
| Python | JSON |
|---|---|
dict | object |
list, tuple | array |
str | string |
int, float | number |
True | true |
False | false |
None | null |
Tuples round-trip as lists — the tuple-ness is lost. Sets, custom classes, and datetime objects can't be serialized by default; you get TypeError: Object of type X is not JSON serializable.
Handling datetime and Custom Objects
Two common approaches.
Convert to a JSON-safe structure first. Do the transformation in your own code, then serialize a plain dict:
Pass a default= function. json.dumps calls it for each value it doesn't know how to serialize:
When reading the data back, you need to convert those ISO strings to datetime yourself — JSON doesn't remember what they originally were.
Round-Tripping a Dict Through JSON
A quick sanity check that dicts survive:
Values are equal, but they're different objects. That's often what you want — json.dumps + json.loads is a cheap way to make a deep copy of any JSON-compatible structure.
A Realistic Example
A small script that loads a JSON config, updates a field, and saves it back:
import json
from pathlib import Path
config_path = Path("settings.json")
# Load (with a default if the file doesn't exist).
if config_path.exists():
config = json.loads(config_path.read_text())
else:
config = {"theme": "dark", "last_opened": None}
# Update.
config["last_opened"] = "2026-01-15"
# Save, pretty-printed.
config_path.write_text(json.dumps(config, indent=2, ensure_ascii=False))
That's a complete "read, modify, write" JSON cycle in a dozen lines.
A Few Habits
- Use
with open(...)for files, always. JSON is just text; all the file-handling rules apply. - Prefer
indent=2for files humans read — config, fixtures, exported data. Skip it for network traffic where compactness matters. - Set
ensure_ascii=Falsefor UTF-8 output so names with accents or non-Latin characters stay readable. - Validate with
try/except json.JSONDecodeErrorwhen parsing data you didn't produce.
Up Next
JSON handles key-value data. The next tool in the same chapter is CSV — Python's built-in support for the tabular format behind most spreadsheet exports you'll encounter.
Frequently Asked Questions
How do I parse JSON in Python?
Use json.loads(text) for a JSON string or json.load(file) for a file object. Both return a Python dict (or list, depending on the JSON). Example: data = json.loads('{"name": "Rosa"}') — data['name'] is 'Rosa'.
How do I convert a Python dictionary to JSON?
json.dumps(my_dict) returns a JSON string. json.dump(my_dict, file) writes directly to a file object. Pass indent=2 for pretty-printed output: json.dumps(data, indent=2).
What's the difference between json.loads and json.load?
loads (with the s) takes a string. load (no s) takes a file object. Same for dumps vs dump. The s is for string; it's the easiest way to remember which is which.
How do I handle dates and custom objects in JSON?
JSON has no date type, so pass dates as ISO-8601 strings and parse them back manually. For custom classes, either provide a default= function to json.dumps that returns a JSON-safe representation, or convert to a dict yourself before serializing.