Python: Working with CSV and JSON Files

Sooner or later every script produces data worth keeping: a class list, an API response, a config file. Python CSV JSON support is built in for exactly that moment, with csv for spreadsheet-style rows and json for nested data. No pip install, just an import. This post covers reading and writing both formats, then converting between them.

“Data is a precious thing and will last longer than the systems themselves.”

Tim Berners-Lee

Last Updated: July 2026 | Tested on: Python 3.14.6 | Difficulty: Beginner | Reading Time: 17 minutes

Here is the everyday way to picture the two formats. CSV is a spreadsheet: neat rows and columns, every row the same shape, like the attendance sheet a teacher passes around. JSON is more like a contact card: one person can have a name, a few phone numbers, a home address, and a list of hobbies all tucked inside the same card. Flat grid versus a card with pockets. Pick the one that matches the shape of your data.

CSV stands for Comma-Separated Values, and it is the universal language of spreadsheets, database exports, and data science datasets. JSON stands for JavaScript Object Notation, and it is what web APIs, config files, and most NoSQL databases speak. This post walks through reading and writing both, from the very first import to a real conversion you will actually use at work.

No Install Needed: Both Modules Are Built In

Most tool posts start with a pip install line. Not this one. The csv and json modules ship with Python itself, like a phone that comes with a camera app already installed: no store visit, just open it and use it. The only setup is an import at the top of your file. Run this quick check to confirm both are ready on your machine.

📄 verify_modules.py: confirm csv and json are available

import csv
import json
import sys

print(f"Python: {sys.version.split()[0]}")
print(f"csv module ready:  {csv.__name__}")
print(f"json module ready: {json.__name__}")

▶ Output

Python: 3.14.6
csv module ready:  csv
json module ready: json

What happened here: both imports succeeded without errors, so you are good to go. Your Python version line may read a different patch number, and that is fine. Anything on the 3.x line from the last few years has these two modules. If an import ever fails here, your Python install itself is broken, not the module.

Quick Win: Read a CSV File

When to UseJSON: Hierarchical / Nestedusers listtotal: 3name: Rahulskills: Python, Goname: Niranjanskills: ReactCSV: Tabular / Flatname, age, cityheader rowRahul, 28, MumbaiNiranjan, 25, PuneViraj, 30, NagpurCSVSpreadsheetsDatabase exportsData analysisFlat rows and columnsJSONAPI responsesConfig filesNested dataWeb applicationsPython CSV vs JSON: Flat Rows vs Nested Objects, and When to Use Each

Tip: click the diagram to open it full screen, then use the zoom and pan controls for a closer look.

The diagram lays the two formats side by side. CSV on the left is a flat grid: a header row, then a row per record, every row the same width. JSON on the right is a tree: a users list that holds objects, and each object can hold its own list of skills. That shape difference is the whole story. CSV suits anything that fits a spreadsheet, while JSON suits anything that nests. Let us start with the quick win: reading a CSV file with the csv module. The script below first builds a small class register of four students, Rahul, Niranjan, Viraj, and Pravin, then reads it back.

📄 read_csv.py: reading a CSV file

import csv

# Create a sample CSV file first
with open("students.csv", "w", newline="", encoding="utf-8") as f:
    f.write("name,age,score,city\n")
    f.write("Rahul Mahadik,28,92,Mumbai\n")
    f.write("Niranjan Raut,25,88,Pune\n")
    f.write("Viraj Patil,30,95,Nagpur\n")
    f.write("Pravin Sharma,23,78,Delhi\n")

# Read with csv.reader, which returns a list per row
with open("students.csv", "r", encoding="utf-8") as f:
    reader = csv.reader(f)
    header = next(reader)  # First row is the header
    print(f"Columns: {header}")
    print()
    for row in reader:
        name, age, score, city = row
        print(f"  {name} (age {age}) from {city}: {score}")

▶ Output

Columns: ['name', 'age', 'score', 'city']

  Rahul Mahadik (age 28) from Mumbai: 92
  Niranjan Raut (age 25) from Pune: 88
  Viraj Patil (age 30) from Nagpur: 95
  Pravin Sharma (age 23) from Delhi: 78

What happened here: csv.reader() turns each line into a list of strings. next(reader) pulls off the header row so the loop starts at the real data. Every row after that is a list like ['Rahul Mahadik', '28', '92', 'Mumbai']. One thing to watch: every value comes back as a string, even the numbers, so you convert age and score to int yourself when you need to do math. The newline="" argument keeps Windows from sneaking blank lines between rows, which we will come back to later.

Writing Data to CSV

Reading is half the job. The other half is writing your own data back out. Think of csv.writer as a careful typist: you dictate the values, and it places every comma, quote, and line ending for you, so you hand it plain Python lists and it produces a clean file. Here we save scores for three more students, Anvi, Anvay, and Vinay.

📄 write_csv.py: writing rows to a CSV file

import csv

scores = [
    ["Anvi", 28, 91, "Kolhapur"],
    ["Anvay", 26, 85, "Nashik"],
    ["Vinay", 24, 73, "Lucknow"],
]

with open("new_students.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.writer(f)
    writer.writerow(["name", "age", "score", "city"])  # Header
    writer.writerows(scores)  # Multiple rows at once

# Verify
with open("new_students.csv", encoding="utf-8") as f:
    print(f.read())

▶ Output

name,age,score,city
Anvi,28,91,Kolhapur
Anvay,26,85,Nashik
Vinay,24,73,Lucknow

What happened here: writerow() writes a single list as one line, and writerows() writes a whole list of lists in one call. You did not type a single comma yourself; the writer placed them. If a value had contained a comma, like a city written as “Pune, Maharashtra”, the writer would have wrapped it in quotes automatically so the file still parses correctly. That auto-quoting is the main reason you reach for the csv module instead of gluing strings together by hand.

DictReader and DictWriter, the Better Way

Grabbing columns by position, like row[2], is brittle. Add one column to the file and every index after it shifts, so your code silently reads the wrong field. DictReader fixes this by handing you a dictionary per row, with the header names as keys. Think of it like a coat check: instead of “give me whatever is on hook number 2,” you ask for “give me the coat tagged score.” The tag does not move when the rack grows.

📄 dict_csv.py: access columns by name, not index

import csv

# DictReader turns each row into a dictionary
with open("students.csv", "r", encoding="utf-8") as f:
    reader = csv.DictReader(f)
    print(f"Fields: {reader.fieldnames}")
    print()
    for row in reader:
        # Access by column name, much cleaner
        if int(row["score"]) >= 90:
            print(f"  [TOP] {row['name']} scored {row['score']}, Top performer!")
        else:
            print(f"  {row['name']} scored {row['score']}")

print()

# DictWriter writes from dictionaries
team = [
    {"name": "Rahul", "role": "Backend", "experience": 5},
    {"name": "Niranjan", "role": "Frontend", "experience": 3},
    {"name": "Aviraj", "role": "DevOps", "experience": 4},
]

with open("team.csv", "w", newline="", encoding="utf-8") as f:
    fields = ["name", "role", "experience"]
    writer = csv.DictWriter(f, fieldnames=fields)
    writer.writeheader()
    writer.writerows(team)

with open("team.csv", encoding="utf-8") as f:
    print(f.read())

▶ Output

Fields: ['name', 'age', 'score', 'city']

  [TOP] Rahul Mahadik scored 92, Top performer!
  Niranjan Raut scored 88
  [TOP] Viraj Patil scored 95, Top performer!
  Pravin Sharma scored 78

name,role,experience
Rahul,Backend,5
Niranjan,Frontend,3
Aviraj,DevOps,4

What happened here: DictReader read the header automatically, so reader.fieldnames lists the columns and each row is a dict you index by name like row["score"]. On the write side, DictWriter needs you to declare the fieldnames up front so it knows the column order, then writeheader() prints that row and writerows() fills in the rest, here saving a three-person dev team of Rahul, Niranjan, and Aviraj. A small but real detail: I used [TOP] as a plain-text marker rather than a fancy star symbol, because emoji and other non-ASCII (American Standard Code for Information Interchange) characters can crash a script with a UnicodeEncodeError on a default Windows terminal.

Plain text always prints. For nine cases out of ten, DictReader and DictWriter beat the plain reader and writer: your code says row["name"] instead of row[0], and adding a column to the file does not break anything.

Reading JSON Files

Now switch to the other module. If CSV is a single sheet of paper, JSON is a set of nesting boxes: open one box and there can be another labelled box inside. Lists inside dictionaries inside more lists, as deep as you like. The json module reads all of that and hands you ordinary Python objects, no manual parsing required.

📄 read_json.py: parse a JSON file

import json

# Create a sample JSON file
config = {
    "app_name": "TechnoScripts",
    "version": "2.0",
    "debug": False,
    "max_users": 1000,
    "features": ["blog", "tutorials", "playground"],
    "database": {
        "host": "localhost",
        "port": 5432,
        "name": "techno_db"
    }
}

with open("config.json", "w", encoding="utf-8") as f:
    json.dump(config, f, indent=2)

# Read it back
with open("config.json", "r", encoding="utf-8") as f:
    data = json.load(f)

print(f"App: {data['app_name']} v{data['version']}")
print(f"Debug: {data['debug']}")
print(f"Features: {data['features']}")
print(f"DB Host: {data['database']['host']}")
print(f"Type of data: {type(data)}")

▶ Output

App: TechnoScripts v2.0
Debug: False
Features: ['blog', 'tutorials', 'playground']
DB Host: localhost
Type of data: <class 'dict'>

What happened here: json.load(f) read the file and translated it straight into Python objects. A JSON object becomes a dict, a JSON array becomes a list, true and false become True and False, and null becomes None. The nested database object came through as a dict inside a dict, which is why data['database']['host'] reaches in two levels deep with no extra work. Notice the type at the end is dict, so once it is loaded you treat it like any other dictionary you have already met.

Writing JSON Data

Writing JSON is the mirror image of reading it. You hand the module a Python object and it produces JSON text, either straight into a file or as a string you can pass around. The example also parses a raw JSON string describing a user named Aditi, which is exactly the kind of text a web API sends you.

📄 write_json.py: turn Python data into JSON

import json

students = [
    {"name": "Rahul", "scores": [92, 88, 95], "passed": True},
    {"name": "Pravin", "scores": [78, 82, 70], "passed": True},
    {"name": "Vinay", "scores": [45, 38, 50], "passed": False},
]

# Write to a file, with indent for readability
with open("students.json", "w", encoding="utf-8") as f:
    json.dump(students, f, indent=2, ensure_ascii=False)

# Convert to a string instead of a file
json_string = json.dumps(students[0], indent=2)
print("JSON string:")
print(json_string)

print()

# Parse JSON from a string
raw = '{"name": "Aditi", "age": 29, "active": true}'
parsed = json.loads(raw)
print(f"Parsed: {parsed}")
print(f"Name: {parsed['name']}, Type: {type(parsed)}")

▶ Output

JSON string:
{
  "name": "Rahul",
  "scores": [
    92,
    88,
    95
  ],
  "passed": true
}

Parsed: {'name': 'Aditi', 'age': 29, 'active': True}
Name: Aditi, Type: <class 'dict'>

What happened here: there are four functions and the names are easy to keep straight once you spot the pattern. json.dump() writes to a file. json.dumps() returns a string, and the extra “s” stands for “string”. json.load() reads from a file. json.loads() parses a string. Notice that Python’s True came out as JSON’s lowercase true on the way out, and the string’s true came back as Python’s True on the way in. The module handles that translation in both directions so you never write it yourself.

JSON with Nested Data

Real API responses are rarely flat. They wrap your data in a status field, a data field, a list of items, and so on. Digging through one is like opening folders on your computer: Documents, then Projects, then the file you want. Each key takes you one level deeper. Here is what a typical response looks like and how you dig the useful parts out of it.

📄 nested_json.py: working with API-style nested JSON

import json

# Simulated API response
api_response = '''
{
  "status": "success",
  "data": {
    "users": [
      {"id": 1, "name": "Rahul Mahadik", "skills": ["Python", "Django", "PostgreSQL"]},
      {"id": 2, "name": "Niranjan Raut", "skills": ["React", "TypeScript", "CSS"]},
      {"id": 3, "name": "Viraj Patil", "skills": ["Docker", "Kubernetes", "AWS"]}
    ],
    "total": 3,
    "page": 1
  }
}
'''

data = json.loads(api_response)

# Navigate the nested structure
print(f"Status: {data['status']}")
print(f"Total users: {data['data']['total']}")
print()

for user in data["data"]["users"]:
    skills = ", ".join(user["skills"])
    print(f"  {user['name']}: {skills}")

▶ Output

Status: success
Total users: 3

  Rahul Mahadik: Python, Django, PostgreSQL
  Niranjan Raut: React, TypeScript, CSS
  Viraj Patil: Docker, Kubernetes, AWS

What happened here: json.loads() parsed the whole response in one shot, then you walked down the tree key by key. data["data"]["users"] reached the list of user objects, and looping over it gave you one dict per user. The ", ".join(user["skills"]) bit took each user’s skills list and glued it into a single readable string. This drill-down pattern, parse once then index your way to the part you need, is exactly how you handle real responses from requests later in the series.

Python CSV JSON Comparison: When to Use Which

You have now seen both halves of the Python CSV JSON toolkit in action. So which one do you pick for a given job? This table lines them up on the things that actually decide it in practice.

CriteriaCSVJSON
StructureFlat, tabular (rows and columns)Hierarchical, nested
Data typesEverything is a stringStrings, numbers, booleans, null, arrays, objects
Human readableEasy (spreadsheet-like)Easy (key-value pairs)
File sizeSmaller (no repeated keys)Larger (keys repeat per object)
Opens in Excel?Yes, nativelyNot easily
APIsRarely usedThe standard format
Config filesUnusualVery common
Python modulecsv (built in)json (built in)
Best forSpreadsheets, databases, data analysisAPIs, configs, nested data, web apps

Quick rule: if your data fits in a spreadsheet, with flat rows and columns, reach for CSV. If your data nests, mixes types, or comes from or goes to an API, reach for JSON.

Practical Workflow: API Data to CSV

Here is a task you will hit for real: an API hands you nested JSON, but your teammate wants a CSV they can open in Excel. It is like copying details from a stack of contact cards into a plain register, one row per card. The trick is flattening, turning a nested field, like a skills list, into a single cell. This little script is the Python CSV JSON bridge you will reuse for years.

📄 json_to_csv.py: convert nested JSON into flat CSV

import json
import csv

# Simulated API data (nested JSON)
api_data = [
    {"name": "Rahul", "department": "Engineering", "skills": ["Python", "Go"], "rating": 4.8},
    {"name": "Niranjan", "department": "Engineering", "skills": ["React", "Node"], "rating": 4.5},
    {"name": "Aditi", "department": "QA", "skills": ["Selenium", "Python"], "rating": 4.2},
]

# Flatten and write to CSV
with open("employees.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.writer(f)
    writer.writerow(["name", "department", "skills", "rating"])
    for emp in api_data:
        # Flatten the skills list into a semicolon-separated string
        skills_str = "; ".join(emp["skills"])
        writer.writerow([emp["name"], emp["department"], skills_str, emp["rating"]])

# Verify
with open("employees.csv", encoding="utf-8") as f:
    print(f.read())

▶ Output

name,department,skills,rating
Rahul,Engineering,Python; Go,4.8
Niranjan,Engineering,React; Node,4.5
Aditi,QA,Selenium; Python,4.2

What happened here: CSV has no idea what a list is, so the skills array had to become text before it could fit in a cell. The "; ".join(emp["skills"]) line did that, joining the items with a semicolon so the comma stays free as the column separator. Everything else dropped straight in, since name, department, and rating were already flat values. This flatten-then-write step is the heart of almost every JSON-to-CSV job you will do.

Common Mistakes

Mistake 1: Forgetting newline="" when writing CSV on Windows

Leave out newline="" and Windows inserts an extra blank line between every row. The cause is a double newline: the csv writer adds its own line ending, and Windows adds another on top. Always pass newline="" in the open() call when you write CSV, and the problem disappears.

Mistake 2: Assuming CSV values keep their type

CSV is text and nothing but text. The number 42 comes back as the string "42", not an integer, so "42" + 8 would raise a TypeError. Convert before you compute: int(row["age"]) for whole numbers, float(row["price"]) for decimals.

Mistake 3: Mixing up json.dump() and json.dumps()

dump() writes to a file. dumps() returns a string. The same split holds for load() versus loads(). The trailing “s” always means “string”, so when in doubt, ask yourself whether you are dealing with a file object or plain text.

Best Practices

  • DO prefer DictReader and DictWriter over the plain reader and writer for CSV.
  • DO always pass newline="" and encoding="utf-8" when you open CSV files.
  • DO use indent=2 in json.dump() for output a human can actually read.
  • DO add ensure_ascii=False in json.dump() when your data has non-English text, so it stays readable instead of turning into escape codes.
  • DO NOT assume CSV values have types. Convert them explicitly with int() or float().
  • DO NOT split strings by hand to parse CSV. The csv module already handles quoting and escaping for you.

Practice Exercises

  1. Exercise 1: Read students.csv with DictReader and print only the students whose score is above the class average. You will need two passes, or one pass that stores the rows in a list first.
  2. Exercise 2: Write a function load_config(path) that reads a JSON config file, returns it as a dict, and returns an empty dict instead of crashing if the file is missing.
  3. Exercise 3: Build a tiny tool that reads employees.csv back in and writes it out as JSON, turning the semicolon-separated skills cell back into a real list.

Conclusion

CSV is for flat, tabular data, the spreadsheet-and-database-export world. JSON is for structured, nested data, the API-and-config-file world. Both modules ship with Python, so there is nothing to install, just an import. Lean on DictReader for CSV so you read columns by name, and on json.load() and json.dump() for JSON. Between them, the Python CSV JSON modules cover the large majority of the data you will move in and out of your programs.

Next up: Exception Handling, what to do when things go wrong, and they always do. And if you want the full roadmap of every post, from first print to machine learning, visit the Python + AI/ML tutorial series home.

Frequently Asked Questions

How do I read a CSV file in Python?

Use import csv then csv.DictReader(open('file.csv')) to read rows as dictionaries. Each row becomes a dict with the column headers as keys. Always open with encoding='utf-8' so accented names and symbols load correctly.

How do I read a JSON file in Python?

Use import json then json.load(open('file.json')). JSON objects become Python dicts, arrays become lists, and types like booleans, numbers, and null are converted automatically.

What is the difference between json.dump and json.dumps?

json.dump(data, file) writes directly to a file. json.dumps(data) returns a JSON string. Same pattern on the read side: json.load(file) reads from a file, json.loads(string) parses a string. The trailing ‘s’ means ‘string’.

When should I use CSV vs JSON?

Use CSV for flat tabular data such as spreadsheets and database exports. Use JSON for nested or hierarchical data such as API responses and config files. In the Python CSV JSON trade-off, CSV files are smaller and JSON is more flexible.

Why do I need newline=” when writing CSV in Python?

On Windows, the csv writer adds its own line endings. Without newline='', the operating system adds an extra line ending on top, which leaves a blank row between every data row.

How do I convert JSON to CSV in Python?

Read the JSON with json.load(), then flatten any nested fields and write the result with csv.DictWriter(). Lists have to be joined into a single string first, since CSV has no native way to store an array in one cell.

Interview Questions on CSV and JSON in Python

Scenario questions, not trivia: this is the form this topic takes in a real interview.

Q: A teammate is exporting addresses like “Pune, Maharashtra” to CSV and worries the extra comma will break the file. Will it?

Not if the file is written with csv.writer or DictWriter. The module automatically wraps any field containing a comma, quote, or newline in double quotes, and csv.reader unwraps them correctly on the way back in. It only breaks when someone builds lines by hand with string concatenation or ",".join(). That auto-quoting is the single biggest reason to always use the csv module instead of manual string splitting.

Q: Your script calls json.dump() on a dict containing a datetime object and crashes with “Object of type datetime is not JSON serializable”. How do you fix it?

JSON has no date type, only strings, numbers, booleans, null, arrays, and objects. The clean fix is converting the datetime yourself before dumping, usually with .isoformat(). A quicker fix is passing default=str to json.dump(), which tells the module to stringify anything it cannot serialize. On the read side, you turn the string back with datetime.fromisoformat().

Q: You call json.load() on a very large JSON file and memory usage spikes until the process is killed. What do you check or change first?

json.load() parses the entire document into Python objects in one go, and those objects can take several times more memory than the file itself. First check whether you actually need everything at once. If not, switch the data to JSON Lines format (one object per line) and parse line by line with json.loads(), or use a streaming parser like ijson. For repeated queries over huge data, loading it into a database is usually the real answer.

Q: Why do experienced developers prefer DictReader over csv.reader in production code?

With csv.reader you access columns by index, so row[2] silently reads the wrong field the moment someone inserts a new column into the file. DictReader keys each row by the header names, so row["score"] keeps working no matter where the column sits. It also makes the code self-documenting: a reviewer sees exactly which field you meant.

Q: What happens to integer dictionary keys when you dump a dict to JSON and load it back?

JSON object keys must be strings, so json.dump() silently converts a key like 1 into "1", and json.load() gives you the string back. The round trip is not exact: {1: "a"} comes back as {"1": "a"}. If your logic depends on integer keys, convert them back yourself after loading, or restructure the data as a list of objects.

Q: How would you store a list, like a person’s skills, inside a single CSV cell?

CSV cells hold plain text only, so you flatten the list into one delimited string on write, for example "; ".join(skills), and split it back with .split("; ") on read. Pick a delimiter that is not a comma so it does not fight the column separator. If you find yourself packing lots of nested data into cells this way, that is a strong sign the data belongs in JSON instead.

Further reading: the official Python documentation is the authoritative source on this.

Previous: Python: File Handling, Reading and Writing Text Files

Next: Python: Exception Handling, Reading Tracebacks & try/except/else/finally

Series Home: Python + AI/ML Tutorial Series

RahulAuthor posts

Avatar for Rahul

Rahul is a passionate IT professional who loves to sharing his knowledge with others and inspiring them to expand their technical knowledge. Rahul's current objective is to write informative and easy-to-understand articles to help people avoid day-to-day technical issues altogether. Follow Rahul's blog to stay informed on the latest trends in IT and gain insights into how to tackle complex technical issues. Whether you're a beginner or an expert in the field, Rahul's articles are sure to leave you feeling inspired and informed.

No comment

Leave a Reply

Your email address will not be published. Required fields are marked *