What you'll learn
- Convert between Python objects and JSON text with
json.dumpsandjson.loads - Read and write JSON files, and know how each Python type maps to JSON
- Read and write CSV with
csv.DictReaderandcsv.DictWriter - Handle the details that break real files: nested data, quoting, and types that are all strings
Concept
JSON is the text format of the web. Every API returns it, every config file that is not TOML or YAML uses it, and it maps almost one to one onto the Python types you know: objects are dictionaries, arrays are lists, strings and numbers are themselves, true/false/null are True/False/None. The json module converts in both directions: dumps (dump to string) and loads (load from string) for text, dump and load for files.
| JSON | Python | Note |
|---|---|---|
{...} |
dict |
keys become strings |
[...] |
list |
tuples become lists on the way out |
"text" |
str |
|
42, 3.5 |
int, float |
|
true, false |
True, False |
|
null |
None |
|
| (none) | datetime, set, your classes |
not JSON: convert first |
The gaps in that table matter. JSON has no dates, no sets, and no idea what your classes are. json.dumps(datetime.now()) raises a TypeError. The fix is to convert at the boundary: turn a datetime into an ISO string before dumping and parse it back after loading, or pass default=str to dumps as a blunt fallback. Nested data is fine at any depth, and indent=2 makes the output readable for humans and diffs.

CSV is the format of spreadsheets: rows of values separated by commas, one record per line, usually with a header row naming the columns. It looks simple enough to handle with split(","), and that works until a value contains a comma, a quote, or a line break. The csv module handles all of that. csv.DictReader gives each row as a dictionary keyed by the header, and csv.DictWriter writes dictionaries back out, quoting whatever needs quoting.
The catch with CSV is that everything arrives as a string. A column of prices is a column of "12.50", not 12.5, and a column of dates is text. Convert types as you read, in the same place, so the rest of the program handles real numbers and dates. Always open CSV files with newline="", which lets the module handle line endings correctly on every platform, and pass encoding="utf-8" as with any text file.

Code
JSON in both directions, with nested data and readable indentation. Note the tuple that comes back as a list and the key that comes back as a string.
# json_basics.py
import json
record = {"name": "ada", "tags": ("python", "math"), "scores": {1: 91, 2: 88}, "active": True, "notes": None}
text = json.dumps(record, indent=2)
print(text)
back = json.loads(text)
print(back["tags"], type(back["tags"]).__name__)
print(list(back["scores"].keys()), back["notes"])
{
"name": "ada",
"tags": [
"python",
"math"
],
"scores": {
"1": 91,
"2": 88
},
"active": true,
"notes": null
}
['python', 'math'] list
['1', '2'] NoneJSON files and the types JSON lacks. The datetime is converted to ISO text before writing and parsed back after reading; the raw attempt shows the error you would otherwise get.
# json_files.py
import json
from datetime import datetime
from pathlib import Path
event = {"title": "launch", "when": datetime(2026, 9, 8, 14, 30), "attendees": {"ada", "linus"}}
try:
json.dumps(event)
except TypeError as err:
print("cannot serialize:", err)
ready = {**event, "when": event["when"].isoformat(), "attendees": sorted(event["attendees"])}
with open("event.json", "w", encoding="utf-8") as f:
json.dump(ready, f, indent=2)
with open("event.json", encoding="utf-8") as f:
loaded = json.load(f)
loaded["when"] = datetime.fromisoformat(loaded["when"])
print(loaded["when"].strftime("%d %b %Y %H:%M"), loaded["attendees"])
print(Path("event.json").read_text(encoding="utf-8").count("\n"), "lines on disk")
cannot serialize: Object of type datetime is not JSON serializable
08 Sep 2026 14:30 ['ada', 'linus']
7 lines on diskWriting and reading CSV with dictionaries. The address contains a comma and gets quoted automatically; on the way back everything is a string until you convert it.
# csv_basics.py
import csv
rows = [
{"name": "ada", "amount": 12.5, "address": "1 Lovelace Rd, London"},
{"name": "linus", "amount": 899, "address": "Helsinki"},
]
with open("orders.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["name", "amount", "address"])
writer.writeheader()
writer.writerows(rows)
print(open("orders.csv", encoding="utf-8").read(), end="")
with open("orders.csv", newline="", encoding="utf-8") as f:
for row in csv.DictReader(f):
print(row["name"], repr(row["amount"]), "->", float(row["amount"]) * 2)
name,amount,address
ada,12.5,"1 Lovelace Rd, London"
linus,899,Helsinki
ada '12.5' -> 25.0
linus '899' -> 1798.0A small pipeline: CSV in, types converted at the boundary, grouped totals out as JSON. This is the shape of a great many real scripts.
# csv_to_json.py
import csv
import json
from pathlib import Path
Path("sales.csv").write_text(
"day,region,amount\n2026-09-01,north,120.50\n2026-09-01,south,80\n2026-09-02,north,45.25\n",
encoding="utf-8",
)
totals = {}
with open("sales.csv", newline="", encoding="utf-8") as f:
for row in csv.DictReader(f):
amount = float(row["amount"])
totals[row["region"]] = round(totals.get(row["region"], 0) + amount, 2)
Path("totals.json").write_text(json.dumps(totals, indent=2), encoding="utf-8")
print(Path("totals.json").read_text(encoding="utf-8"))
{
"north": 165.75,
"south": 80.0
}Try it yourself
Create contacts_io.py. Build a list of four contact dictionaries with a name, an email, a list of tags, and a joined date. Save them to contacts.json (converting the dates), load the file back and restore the dates, then write the same contacts to contacts.csv with the tags joined by ;. Read the CSV back with DictReader, split the tags again, and print each contact's name with how many tags it has.
Common mistakes
- Splitting CSV lines by hand.
line.split(",")breaks on the first quoted comma; always use thecsvmodule. - Forgetting that CSV values are strings.
row["amount"] > 100compares text; convert withfloatorintwhen you read. - Dumping datetimes or sets to JSON. Both raise
TypeError; convert to ISO strings and lists at the boundary, and convert back after loading.
What's next
json.dumps/loadsanddump/loadmap dictionaries, lists, strings, numbers, booleans, andNoneto JSON; convert dates, sets, and objects at the edge.csv.DictReaderandDictWriterhandle quoting and headers; open withnewline=""andencoding="utf-8".- CSV gives you strings; convert types where the data enters the program.
Files of records are covered. When records need to be queried, filtered, and updated by many programs, they belong in a database, and Python ships one: sqlite3.