Suppose your school's result portal lets you download your class's marks in two different ways: one button says "Download as CSV" and another says "Download as JSON." Both files describe the exact same marks for the exact same students — but if you open them in a text editor, they look nothing alike. One is a plain list of comma-separated lines that looks like a spreadsheet with the grid lines removed. The other is full of curly braces, square brackets, and quotation marks that looks more like a nested set of labelled boxes. A program that reads student records from a bank's transaction history, a train-booking app's seat-availability response, or a weather app's forecast is doing the same basic thing every time: pulling structured data out of a file written in one of these two formats. This chapter teaches you to read, write, and convert between both — precisely, not just "kind of."
What a CSV file actually is
CSV stands for Comma-Separated Values. It is one of the oldest and simplest ways to store tabular data — data that fits naturally into rows and columns, like a spreadsheet. Here is a CSV file named marks.csv holding three students' exam records:
Name,Subject,Marks
Aditi Sharma,Mathematics,92
Rohan Verma,Science,85
Priya Nair,English,78
Look closely at the rules this file follows, because every one of them matters when you write code to read it:
- The first line is a header row. It names the columns —
Name,Subject,Marks— but it is not itself a record; it is a label for every row that follows. - Every line after the header is one record (one student, in this case), and every record has the same number of fields, in the same order, as the header.
- Fields are separated by commas, and nothing else marks where one field ends and the next begins.
- The file is plain text. There is no formatting, no bold, no cell colour — just characters. This is exactly why CSV is so portable: literally any programming language, spreadsheet program, or database can read a file made of commas and line breaks, without needing to understand some proprietary format.
That last rule — "nothing but commas separate fields" — creates an obvious problem. What if a student's name itself contains a comma, such as Sharma, Aditi (surname first)? Written naively, that would produce Sharma, Aditi,Mathematics,92, and now it looks like there are four fields instead of three. The CSV standard solves this by wrapping any field that contains a comma in double quotes: "Sharma, Aditi",Mathematics,92. A properly written CSV reader knows that a comma inside a quoted field is part of the data, not a separator. This is a genuine, common source of bugs for students who try to write their own CSV parser with something naive like line.split(",") — it silently breaks the moment a name or address contains a comma. Always use a tested library instead of hand-rolling this logic; Python's built-in csv module already handles quoting correctly.
Reading a CSV file in Python
Python's standard library ships a module called csv specifically for this. The simplest tool it gives you is csv.reader, which turns every line of the file into a plain Python list of strings:
import csv
with open("marks.csv") as file:
reader = csv.reader(file)
for row in reader:
print(row)
Trace this line by line. open("marks.csv") opens the file for reading and hands it to csv.reader, which wraps it so that each iteration of the for loop gives you one line, already split on commas into a list. The four lines of the file become four lists, and print(row) shows each one:
['Name', 'Subject', 'Marks']
['Aditi Sharma', 'Mathematics', '92']
['Rohan Verma', 'Science', '85']
['Priya Nair', 'English', '78']
Notice two things a beginner easily misses. First, the header row comes through as a normal row too — csv.reader does not know or care that the first line is special; your code has to treat it differently if you want to. Second, and more important: every value is a string, even '92'. A CSV file has no concept of "this column holds numbers." Everything is text until you explicitly convert it — for example, int(row[2]) to get the actual number 92 you could do arithmetic with. Forgetting this is one of the most common CSV bugs: writing row[2] + row_two[2] expecting addition, but instead getting string concatenation, e.g. '92' + '85' producing '9285', not 177.
Since the header-as-a-normal-row behaviour is awkward, Python gives you a second, friendlier tool: csv.DictReader. It automatically treats the first line as column names and turns every following row into a dictionary keyed by those names:
import csv
with open("marks.csv") as file:
reader = csv.DictReader(file)
for row in reader:
print(row)
Trace it: DictReader reads the header line first and stores ['Name', 'Subject', 'Marks'] as field names, without handing that line to your loop. Each subsequent line is then zipped up with those field names into a dictionary. In Python 3.8 and later, that dictionary prints as a plain dict, so the output is:
{'Name': 'Aditi Sharma', 'Subject': 'Mathematics', 'Marks': '92'}
{'Name': 'Rohan Verma', 'Subject': 'Science', 'Marks': '85'}
{'Name': 'Priya Nair', 'Subject': 'English', 'Marks': '78'}
Now you can write row["Marks"] instead of remembering that marks live at index 2 — far less error-prone once a file has a dozen columns instead of three. But the values are still strings; DictReader fixes the labelling problem, not the typing problem.
Where CSV runs out of road
CSV is excellent for one very specific shape of data: a flat table where every record has exactly the same fields. But real data is often not flat. Suppose you want to store, for a single student, not just one mark but a whole list of subject-wise marks, along with the student's name and class. In a spreadsheet you would either repeat the student's name on multiple rows (one row per subject) or invent extra columns like Subject1, Marks1, Subject2, Marks2 — both are clumsy, and neither scales if some students take four subjects and others take six. CSV has no built-in way to say "this field itself contains a list" or "this field itself contains another whole record." That gap is exactly what JSON was built to fill.
What a JSON file actually is
JSON stands for JavaScript Object Notation. Despite the name, it is not tied to the JavaScript language — it is a text format for representing structured data that almost every modern programming language and web API can read and write, which is why apps that fetch live data over the internet (a train-ticket app checking seat availability, a weather app pulling a forecast) overwhelmingly use it. Here is the same student's data as JSON, in a file named student.json, but now genuinely nested — one student with a whole list of subjects inside:
{
"name": "Aditi Sharma",
"class": "8",
"subjects": [
{"name": "Mathematics", "marks": 92},
{"name": "Science", "marks": 88},
{"name": "English", "marks": 95}
]
}
Read this structure the way you would read a set of labelled boxes inside boxes:
- The outermost
{ }is an object — an unordered collection of"key": valuepairs, separated by commas. Here the object has three keys:name,class, andsubjects. - The value of
subjectsis not a single number or string — it is an array, marked by[ ], holding a list of items in a fixed order. - Each item inside that array is itself an object —
{"name": "Mathematics", "marks": 92}— with its own keys. This is the nesting CSV cannot express: an object inside an array inside an object. - Notice
92,88, and95are written without quotes. JSON actually distinguishes data types: numbers are unquoted, strings are wrapped in double quotes, and there are alsotrue,false, andnullas their own unquoted types. This is a real advantage over CSV, where "92" the text and 92 the number look identical until you parse them.
JSON's syntax is strict, and this is exactly where a common misconception creeps in. Python dictionaries and JSON objects look almost identical, so many students assume they are interchangeable — they are not quite. A Python dictionary can use single quotes ({'name': 'Aditi'}), can end its last item with a trailing comma, and writes its booleans as True and False with a capital letter, and its null value as None. Valid JSON requires double quotes around every key and every string value, does not allow a trailing comma after the last item, and writes its booleans in lowercase as true/false, with a null written as lowercase null. If you type {'name': 'Aditi',} into a file and try to load it as JSON, it will fail — that syntax is valid Python but invalid JSON. Keep this distinction sharp: Python's json module happily converts between the two representations for you, but it is strict about which one it accepts as input text.
Reading a JSON file in Python
Python's built-in json module mirrors the csv module's style. To read student.json into your program as ordinary Python data — dictionaries, lists, strings, and numbers — you use json.load:
import json
with open("student.json") as file:
data = json.load(file)
print(data["name"])
print(data["subjects"][0]["name"])
Trace this carefully, because the nesting is the whole point. json.load reads the file and converts the outer { } into a Python dictionary, stored in data. So data["name"] looks up the key "name" in that dictionary and gets the string "Aditi Sharma". Meanwhile data["subjects"] is a Python list of three dictionaries, because the JSON array [ ] became a Python list. data["subjects"][0] grabs the first item of that list — the dictionary {"name": "Mathematics", "marks": 92} — and then ["name"] pulls "Mathematics" out of it. So the output is:
Aditi Sharma
Mathematics
Unlike CSV, notice that data["subjects"][0]["marks"] would give you the actual integer 92, not the string "92" — JSON preserved the number type, so you can do arithmetic on it immediately, with no int() conversion needed. Let's use that to compute the class average for this one student, using an explicit loop — the same style of loop you would use to total up marks read from a CSV file:
total = 0
for subject in data["subjects"]:
total = total + subject["marks"]
average = total / len(data["subjects"])
print("Total marks:", total)
print("Average:", round(average, 2))
Trace it: total starts at 0. The loop visits each dictionary in data["subjects"] in turn — first {"name": "Mathematics", "marks": 92}, so subject["marks"] is 92 and total becomes 92. Next is Science with 88, so total becomes 92 + 88 = 180. Then English with 95, so total becomes 180 + 95 = 275. The loop has now visited all three items, so it ends with total equal to 275. len(data["subjects"]) is 3, so average is 275 / 3, which is 91.666..., and round(average, 2) rounds that to 91.67. The output is:
Total marks: 275
Average: 91.67
Converting a CSV file into JSON
A very common real task — for instance, taking a marks sheet exported from a spreadsheet and feeding it into a web app that expects JSON — is converting CSV to JSON. You already have every tool you need: read the CSV into a list of dictionaries with csv.DictReader, then hand that list to json.dumps, which does the reverse of json.load — it turns Python data back into JSON text:
import csv
import json
with open("marks.csv") as file:
reader = csv.DictReader(file)
records = list(reader)
print(json.dumps(records, indent=2))
Trace it. list(reader) forces the DictReader to produce all three rows as a Python list of dictionaries: [{'Name': 'Aditi Sharma', 'Subject': 'Mathematics', 'Marks': '92'}, {'Name': 'Rohan Verma', 'Subject': 'Science', 'Marks': '85'}, {'Name': 'Priya Nair', 'Subject': 'English', 'Marks': '78'}]. Passing that list to json.dumps converts it into JSON text. The indent=2 argument tells json.dumps to pretty-print the result — every nested level (the outer list, and every dictionary inside it) gets expanded onto its own lines and indented by 2 spaces per level, rather than being crammed onto one line. Since each of the three dictionaries has three keys, each dictionary expands to five lines: the opening {, three key lines, and the closing }. The full, exact output is:
[
{
"Name": "Aditi Sharma",
"Subject": "Mathematics",
"Marks": "92"
},
{
"Name": "Rohan Verma",
"Subject": "Science",
"Marks": "85"
},
{
"Name": "Priya Nair",
"Subject": "English",
"Marks": "78"
}
]
Notice that "Marks": "92" keeps its quotes — json.dumps cannot know that the text "92" is meant to be a number, because csv.DictReader handed it a plain string in the first place. If you want real JSON numbers instead of numeric-looking strings, you must convert each mark with int(row["Marks"]) before building the dictionary you pass to json.dumps — the format conversion does not silently fix data types for you.
Converting a JSON file into CSV
The reverse conversion works the same way in mirror image, using csv.DictWriter to write a list of dictionaries back out as comma-separated rows:
import csv
with open("marks_copy.csv", "w", newline="") as file:
fieldnames = ["Name", "Subject", "Marks"]
writer = csv.DictWriter(file, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(records)
Here, fieldnames tells the writer what header row to produce and in what order to place each dictionary's values. writeheader() writes Name,Subject,Marks as the first line. writerows(records) then writes one comma-separated line per dictionary in the records list, in the same order the dictionary's values match the given fieldnames — reproducing the original marks.csv exactly. The newline="" argument is a detail worth remembering precisely because it is easy to skip and produces a subtle bug: without it, on some systems Python's own text-mode line-ending translation combines with the csv module's own line-ending handling, and you end up with a blank line inserted after every row. This is not a CSV-format rule — it is a Python file-handling detail that the csv module's documentation explicitly asks you to work around this way.
The catch: not every JSON file can become a flat CSV
This conversion worked cleanly because marks.csv was already flat — one row, three fields, no nesting. But recall the earlier student.json, where one student had a whole array of subject-and-mark pairs nested inside a single field. There is no single CSV row that can hold "a list of three (subject, mark) pairs" as one cell value without breaking the one-field-one-value rule. To flatten that kind of data into CSV, you have to make a design decision: either repeat the student's name on three separate rows (one per subject — call this "long" format), or create fixed columns like Maths_Marks, Science_Marks, English_Marks (call this "wide" format), and either choice loses some of the structure the JSON file expressed naturally. This is not a limitation of your code — it is a genuine structural limitation of the CSV format itself, and it is exactly why APIs that return richly structured data (nested objects, variable-length lists) almost always choose JSON over CSV.
Choosing between CSV and JSON
- Use CSV when your data is naturally tabular — every record has the same fixed set of fields — and when the consumer of the file is likely to be a spreadsheet program (Excel, Google Sheets) or a simple data-processing script. CSV files are smaller and faster to parse for large flat tables.
- Use JSON when your data has variable structure, optional fields, or genuine nesting — one record containing a list of other records, for instance — and especially when the data will be sent to or received from a web service, since JSON is the near-universal format for API responses.
- Types are preserved in JSON, lost in CSV. A CSV file cannot distinguish the number 92 from the text "92", or represent a boolean or a null value directly — everything is text. JSON keeps numbers, strings, booleans, and null as distinct types.
- Both are plain text and both are readable by virtually every programming language, which is why, despite their differences, they remain the two most common formats for exchanging data between systems that were never designed to talk to each other.
Practice: trace before you run
Work through each of these on paper first — predicting output correctly is the actual skill being tested, not typing code into an interpreter.
- A file
fees.csvcontains:Roll,Name,Fee\n1,Kabir,4500\n2,Sana,5200. What doescsv.readerproduce for the second data row as a Python list, and what type is the fee value in it? - Using
csv.DictReaderon the same file, write the exact dictionary produced for roll number 2. - A JSON file holds
{"school": "DPS", "toppers": [{"name": "Meera", "score": 96}, {"name": "Ishaan", "score": 94}]}. Afterdata = json.load(file), what doesdata["toppers"][1]["score"]evaluate to, and what Python type is it? - Explain, in your own words, why
{'a': 1,}is valid to write directly in a Python program but would raise an error if you tried tojson.loadit from a file. Name both rules being broken. - You convert
fees.csvto JSON usingcsv.DictReaderandjson.dumps(records, indent=2)without first converting the fee to an integer. Will the resulting JSON's"Fee"values be numbers or strings? Justify your answer from howDictReaderreads values. - A JSON file has one object per student with a nested list of exam attempts of varying length — some students have 2 attempts, others have 5. Explain why flattening this directly into a single flat CSV table is not straightforward, and describe one reasonable way to do it anyway.
Summary
CSV stores flat, tabular data as plain-text lines with comma-separated fields; a header row names the columns, and every value is read as a string regardless of what it looks like. Python's csv module reads it with csv.reader (each row a list) or the friendlier csv.DictReader (each row a dictionary keyed by the header), and writes it back with csv.writer or csv.DictWriter. JSON stores data as nested objects ({ }, key-value pairs) and arrays ([ ], ordered lists), preserving real data types — numbers, strings, booleans, and null — and can represent structure, like a variable-length list nested inside a single record, that CSV cannot. Python's json module reads it with json.load and writes it with json.dumps, and because JSON syntax is stricter than a Python dictionary literal (double quotes only, no trailing commas, lowercase true/false/null), it is worth checking that distinction explicitly rather than assuming the two are interchangeable. Converting CSV to JSON is a two-step trip through Python's own data structures — DictReader in, json.dumps out — and the reverse works the same way, but only cleanly when the JSON data is already flat; genuinely nested JSON forces a real design decision about how to flatten it into rows and columns.