Unit 09 · lesson
CSV and JSON
Core path: 30 minutes
Once the program can open a file, how should structured data be represented inside that file?
You could invent your own format:
Nova|850|ELITE
Maya|950|ELITE
Then you would also have to define escaping, missing values, parsing rules, nested data, and every other detail yourself.
Usually, use a format whose rules already exist.
This week focuses on CSV and JSON because they represent two common data shapes differently.
CSV is naturally tabular
CSV means Comma-Separated Values.
A roster can look like:
name,score,rank
Nova,850,ADVANCED
Maya,950,ELITE
Think in rows and columns:
row → one record
column → one field repeated across records
That makes CSV useful for data shaped like a table.
Use Python's csv module rather than manually splitting every line on commas. Real CSV can contain quoted commas and other cases that make hand-written parsing fragile.
Example:
import csv
with open("players.csv", "r", newline="", encoding="utf-8") as file:
reader = csv.DictReader(file)
for row in reader:
print(row["name"], row["score"])
DictReader uses the header row as field names, so each row becomes dictionary-like data.
CSV text still has to be converted into program types
If a CSV contains:
Nova,850
then the score read from the file is typically text:
"850"
If you need numeric operations:
score = int(row["score"])
This is Week 3 again, except the input source is now a file instead of the keyboard.
The same type reasoning keeps returning because data boundaries keep returning.
JSON represents nested structured data naturally
JSON can represent objects/records, arrays/lists, strings, numbers, Booleans, and null values.
Example JSON:
{
"name": "Nova",
"score": 850,
"online": true,
"tags": ["python", "robotics"]
}
It looks similar to Python syntax, which is convenient and dangerous.
JSON is not Python code.
For example:
JSON true ≠ Python True
JSON false ≠ Python False
JSON null ≈ Python None after parsing
The file contains a text representation following JSON's grammar.
Python's json module translates between that representation and Python data structures.
Serialization: Python state becomes a stored representation
import json
player = {
"name": "Nova",
"score": 850,
"online": True,
}
with open("player.json", "w", encoding="utf-8") as file:
json.dump(player, file, indent=2)
The in-memory dictionary is converted into JSON text and written to the file.
That process is serialization.
Conceptually:
Python dictionary/list values
↓ json.dump()
JSON text representation
↓ file write
bytes stored on disk
The Python object itself is not frozen into the file.
A representation of its data is stored.
Deserialization reconstructs new runtime data
Later:
with open("player.json", "r", encoding="utf-8") as file:
player = json.load(file)
Now:
JSON text on disk
↓ read + parse
new Python dictionary/list values in memory
The process that originally created the file may be long gone.
json.load() reconstructs new runtime data from the saved representation.
That is why Week 12 can later rebuild objects from JSON without claiming the original live object somehow survived shutdown.
Prove the representation change without pretending the browser has your files
The browser runtime can safely demonstrate the serialization boundary without pretending it owns a persistent filesystem.
Run this in-memory round trip:
Run the code to see output.
This stays entirely in memory. Use it to prove that serialization creates JSON text and deserialization creates new Python list/dictionary values. It does not simulate a persistent file.
Watch the type evidence carefully:
Python list/dictionaries
↓ json.dumps()
JSON string
↓ json.loads()
new Python list/dictionaries
Nothing in that embedded example is saved to disk. It exists only for the current run. That limitation is intentional.
The real persistence proof still happens in your development environment in Lessons 4 and 5, where you create an actual file, stop the process, and load the file in a later run.
dump versus dumps, load versus loads
The names are annoyingly similar.
A useful distinction:
json.dump(...) → serialize to a file-like object
json.dumps(...) → serialize to a Python string
json.load(...) → parse from a file-like object
json.loads(...) → parse from a Python string
The s versions work with strings.
You do not have to memorize the names by staring at them. Use the data flow to decide which boundary you are crossing.
Malformed JSON is different from missing JSON
Suppose the file exists but contains:
{
"name": "Nova",
"score": 850,
}
That trailing comma makes the JSON invalid.
Opening the file succeeds.
Parsing fails.
So:
filesystem layer → success
JSON syntax layer → failure
This distinction becomes critical with APIs in Week 13: a network request can succeed while the data is still unusable.
Valid JSON can still be wrong for your application
This is valid JSON:
{
"message": "hello"
}
But if your application expects:
[
{"name": "Nova", "score": 850}
]
then the data shape is wrong for your contract.
Parsing success does not prove application validity.
That is why data crossing a boundary should be inspected before you trust every field blindly.
Choose CSV or JSON from the shape
Use CSV naturally when the data is mostly:
rows × columns
Examples:
sensor readings
simple score tables
spreadsheet-style exports
Use JSON naturally when the data has named fields and possibly nested structures:
one player record
list of player records
configuration
API response
nested system state
Neither is universally better.
A flat table in JSON can be fine. A deeply nested record forced into CSV can become awkward quickly.
The data shape should drive the representation.
Follow one roster through both representations
Python:
players = [
{"name": "Nova", "score": 850},
{"name": "Maya", "score": 950},
]
Possible CSV:
name,score
Nova,850
Maya,950
Possible JSON:
[
{"name": "Nova", "score": 850},
{"name": "Maya", "score": 950}
]
Now add:
"skills": ["python", "git"]
inside each player record.
Which format keeps that nested relationship clearer with less convention invented by you?
That is the kind of tradeoff to reason about.
Inspect the boundary
When structured data enters your program, ask:
Did the file open?
Did parsing succeed?
What top-level Python type did I get?
Does the structure contain the required fields?
Are field values the types my logic expects?
Those questions will follow you into APIs almost unchanged.
Vocabulary lab
Flip the idea, not just the card
Explain the term before you reveal the back. Then compare your explanation with the definition, example, and warning.
Read all terms without animation
- CSV
- A text format commonly used to represent tabular rows and columns. Example: name,score followed by data rows. Do not confuse it with: JSON, which naturally represents nested objects and arrays.
- JSON
- A structured text data format used for storage and data exchange. Example: {"name": "Nova", "score": 850}. Do not confuse it with: A live Python dictionary in runtime memory.
- Serialization
- Converting runtime data into a representation that can be stored or transmitted. Example: json.dump(player, file). Do not confuse it with: Deserialization, which reconstructs runtime data from a representation.
- Deserialization
- Parsing stored or transmitted data into runtime data structures. Example: json.load(file) creates Python dictionaries/lists from JSON text. Do not confuse it with: Writing current Python values out to a file.
- Data Contract
- The expected shape, fields, and value types that a program depends on when consuming data. Example: A list of player records that each contain name and integer score fields. Do not confuse it with: Merely being valid JSON syntax.