
Quick answer: Use the csv module with DictReader and DictWriter for spreadsheets, and the built-in json module with json.load/json.dump for structured data. Both read the whole file with one call once opened, and both need encoding="utf-8" and newline="" for CSV so line endings behave on every platform.
Part 7 of our Python series — Module 2. The file handling article comes first if open() still feels unfamiliar.
The two formats you will meet forever
CSV is a flat table: rows and columns, strings everywhere, no types. JSON is a nested tree: objects, arrays, numbers, booleans, null. Every data job you will ever take involves one of them, usually both — an export arrives as CSV and leaves as JSON for an API.
Reading CSV into dictionaries
The naive version treats every value as a string and depends on column order. DictReader fixes the second problem and makes code readable:
import csv
with open("sales.csv", newline="", encoding="utf-8") as fh:
reader = csv.DictReader(fh)
for row in reader:
print(row["region"], row["amount"])The first line becomes the keys. newline="" is not optional decoration: without it, Windows line endings inside quoted fields get mangled.
Converting types as you read
CSV gives you strings, so row["amount"] is "1200", not 1200. Convert at the boundary:
records = []
with open("sales.csv", newline="", encoding="utf-8") as fh:
for row in csv.DictReader(fh):
records.append({
"region": row["region"],
"amount": float(row["amount"]),
"units": int(row["units"]),
})Doing this once, up front, is what stops string arithmetic bugs from spreading through a codebase.
Writing CSV back out
import csv
fieldnames = ["region", "amount", "units"]
with open("summary.csv", "w", newline="", encoding="utf-8") as fh:
writer = csv.DictWriter(fh, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(records)writeheader() writes the column names, writerows() handles escaping — commas inside a field are quoted automatically. Never build CSV lines with f-strings; one address containing a comma corrupts the file.
JSON: the tree format
import json
data = {
"name": "Asha",
"active": True,
"scores": [88, 92, 79],
"address": {"city": "Chennai", "pin": "600001"},
}
with open("profile.json", "w", encoding="utf-8") as fh:
json.dump(data, fh, indent=2) # indent makes it human-readableReading it back returns the same structure with real types restored — True stays a boolean, 88 stays an int:
with open("profile.json", encoding="utf-8") as fh:
loaded = json.load(fh)
loaded["scores"][0] # 88 (int, not "88")
loaded["address"]["city"] # 'Chennai'For API payloads you rarely touch files at all:
text = json.dumps(data) # dict -> JSON string
obj = json.loads(text) # JSON string -> dictComplete executable example
# csv_to_json.py — read a CSV, convert types, enrich, write JSON
import csv
import json
import os
CSV_PATH = "sales.csv"
JSON_PATH = "sales.json"
# seed the CSV so the script runs on a fresh machine
if not os.path.exists(CSV_PATH):
with open(CSV_PATH, "w", newline="", encoding="utf-8") as fh:
fh.write("region,amount,units\n")
fh.write("North,1200.50,10\n")
fh.write("South,830.00,7\n")
fh.write("North,410.25,3\n")
rows = []
with open(CSV_PATH, newline="", encoding="utf-8") as fh:
for row in csv.DictReader(fh):
rows.append({
"region": row["region"].strip(),
"amount": float(row["amount"]),
"units": int(row["units"]),
})
by_region = {}
for r in rows:
bucket = by_region.setdefault(r["region"], {"amount": 0.0, "units": 0})
bucket["amount"] += r["amount"]
bucket["units"] += r["units"]
summary = {
"source": CSV_PATH,
"rows": len(rows),
"regions": sorted(by_region),
"totals": {k: {"amount": round(v["amount"], 2), "units": v["units"]}
for k, v in by_region.items()},
}
with open(JSON_PATH, "w", encoding="utf-8") as fh:
json.dump(summary, fh, indent=2)
print(json.dumps(summary, indent=2))
# round-trip proof: the file parses back to identical data
with open(JSON_PATH, encoding="utf-8") as fh:
again = json.load(fh)
print("round-trip identical:", again == summary)Line by line: seeding writes a real CSV with a header row so DictReader has keys; float/int conversion happens exactly once at the boundary; setdefault accumulates per-region totals without an if key in dict dance; the dict comprehension rounds only for output; the final comparison proves the write is valid JSON that reloads to the same object.
Common mistakes and edge cases
json.JSONDecodeError— the file is not valid JSON (trailing comma, single quotes, unquoted keys). Print the decoder’slineno/colnoand look there.- Forgetting
newline=""on CSV — produces blank lines between rows on Windows and breaks embedded newlines in fields. UnicodeDecodeError— the file is not UTF-8 (often a Windows-1252 export). Open withencoding="cp1252"for that file, or convert it once.KeyErrorfrom a missing column — header names differ (Amountvsamount). Printreader.fieldnamesonce and match exactly.TypeError: Object of type Decimal is not JSON serializable— JSON only knows str/int/float/bool/None/list/dict. Convert Decimal, datetime, or set values before dumping;default=stris the blunt fallback.
Key takeaways and challenge
csv.DictReader/DictWriterfor tables; convert types at the boundary.json.load/json.dumpfor trees;json.loads/json.dumpsfor strings.- Always
encoding="utf-8"; addnewline=""for CSV.
Challenge: extend csv_to_json.py to write a second CSV (totals.csv) with one row per region and a computed average (amount / units), then reload both files and assert the averages match. That is an end-to-end data pipeline in twenty lines.
Want one-to-one help getting ramped in Python? Ampersand Academy offers hands-on training.
Why does Python CSV output have blank lines between rows?
You opened the file without newline=”. Always open CSV files with newline=” and an explicit encoding so the csv module controls line endings on every platform.
What is the difference between json.load and json.loads?
json.load reads from an open file object; json.loads parses a JSON string. The matching writers are json.dump for files and json.dumps for strings.
Why does my CSV value add as text instead of a number?
Every CSV field arrives as a string, so you must convert with int or float at the point of reading. Converting once at the boundary prevents string arithmetic bugs from spreading through your code.
Can Python write a datetime or Decimal value to JSON?
Not directly, because JSON only supports str, int, float, bool, None, list and dict. Convert those values first, or pass default=str to json.dump as a blunt fallback.
How do I handle a CSV file saved in a non-UTF-8 encoding?
Open it with the actual encoding, for example encoding=’cp1252′ for common Windows exports. If your code assumes UTF-8 you will see UnicodeDecodeError on the first unusual character.
Last updated on · Written by Dinesh Kumar R
