Beginner · Lesson 43 of 94

JSON and CSV

Watch this lesson

View full playlist ↗

Read the lesson

What you'll learn

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.

JSON and CSV: unsupported Python types; datetime, set, class → convert first; isoformat, list, to_dict → JSON-ready; Python dict/list → dumps / dump; JSON text / file → loads / load; Python dict/list
JSON and CSV · Figure 1 · Open for full size

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.

JSON and CSV: orders.csv → csv.DictReader: rows as dictionaries of strings → convert: float(row['amount' → 'day' → real Python values → csv.DictWriter: back to text
JSON and CSV · Figure 2 · Open for full size

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"])
Output
{
  "name": "ada",
  "tags": [
    "python",
    "math"
  ],
  "scores": {
    "1": 91,
    "2": 88
  },
  "active": true,
  "notes": null
}
['python', 'math'] list
['1', '2'] None

JSON 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")
Output
cannot serialize: Object of type datetime is not JSON serializable
08 Sep 2026 14:30 ['ada', 'linus']
7 lines on disk

Writing 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)
Output
name,amount,address
ada,12.5,"1 Lovelace Rd, London"
linus,899,Helsinki
ada '12.5' -> 25.0
linus '899' -> 1798.0

A 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"))
Output
{
  "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

What's next

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.

Course overview