Site icon Ampersand Tutorials

Python CSV and JSON Parsing: Read, Write, and Convert (With Examples)

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-readable

Reading 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 -> dict

Complete 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

Key takeaways and challenge

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.

Exit mobile version