---
title: "Read and write JSON, CSV and text files in Python"
description: "Open, read and save text, JSON and CSV files in Python using pathlib and the with statement, plus the encoding and newline settings that stop silent corruption."
url: https://articles.sythra.ai/articles/python-read-write-files-json-csv
slug: python-read-write-files-json-csv
author: "vaibhavkothari"
author_url: https://articles.sythra.ai/writers/vaibhavkothari
date_published: 2026-11-12T21:11:00.153Z
date_modified: 2026-11-12T21:11:00.153Z
topics: ["Python", "Coding"]
reading_time_minutes: 5
publisher: "Sythra"
publisher_url: https://sythra.ai
access: free
language: en
---

# Read and write JSON, CSV and text files in Python

> Open, read and save text, JSON and CSV files in Python using pathlib and the with statement, plus the encoding and newline settings that stop silent corruption.

Source: https://articles.sythra.ai/articles/python-read-write-files-json-csv · Author: vaibhavkothari · Published: 2026-11-12 · Reading time: 5 min · Topics: Python, Coding

Almost every useful script reads something from disk or saves something back. Python makes the three most common formats — plain text, JSON and CSV — easy, but a few defaults can quietly bite you. This guide covers the safe way to do each.

## Paths first: use `pathlib`

Stop gluing paths together with strings. pathlib works the same on Windows, macOS and Linux:

```python
from pathlib import Path

folder = Path("data")
notes = folder / "notes.txt"      # the / joins path pieces

print(notes)            # data\notes.txt on Windows, data/notes.txt elsewhere
print(notes.exists())   # True or False
print(notes.suffix)     # .txt
print(notes.name)       # notes.txt
```

Create the folder before writing into it:

```python
folder.mkdir(exist_ok=True)
```

## Text files

The safe pattern is the `with` statement. It closes the file for you, even if your code crashes halfway:

```python
from pathlib import Path

path = Path("data/notes.txt")

# Write (overwrites anything already there)
path.write_text("first line\nsecond line\n", encoding="utf-8")

# Read everything
text = path.read_text(encoding="utf-8")
print(text)
```

For big files, read line by line instead of loading everything:

```python
with open(path, encoding="utf-8") as f:
    for line in f:
        print(line.rstrip("\n"))
```

To add to the end without erasing, use mode `"a"`:

```python
with open(path, "a", encoding="utf-8") as f:
    f.write("third line\n")
```

### Always pass `encoding="utf-8"`

On Windows the default encoding can be a legacy code page. A file with accents, emoji or non-English names then reads as garbage or raises `UnicodeDecodeError`. Saying `encoding="utf-8"` every time removes the whole class of bug.

## JSON files

JSON maps neatly onto Python dictionaries and lists.

```python
import json
from pathlib import Path

settings = {
    "name": "Sythra",
    "tags": ["python", "ml"],
    "retries": 3,
    "debug": False,
}

path = Path("settings.json")

# Save
with open(path, "w", encoding="utf-8") as f:
    json.dump(settings, f, indent=2, ensure_ascii=False)

# Load
with open(path, encoding="utf-8") as f:
    loaded = json.load(f)

print(loaded["tags"])   # ['python', 'ml']
```

Two options worth knowing:

- `indent=2` makes the file readable for humans and tidy in version control.
- `ensure_ascii=False` keeps characters like `é` readable instead of writing `é`.

JSON only supports basic types. A `datetime`, `set` or custom object raises `TypeError: Object of type ... is not JSON serializable`. Convert it first — for example `date.isoformat()` or `list(my_set)`.

If a file is empty or hand-edited and broken, `json.load` raises `json.JSONDecodeError`. Catch it when the file comes from a user:

```python
try:
    with open("settings.json", encoding="utf-8") as f:
        data = json.load(f)
except FileNotFoundError:
    data = {}
except json.JSONDecodeError as error:
    print(f"settings.json is not valid JSON: {error}")
    data = {}
```

## CSV files

Python's `csv` module handles quoting and commas inside values, which naive `line.split(",")` gets wrong.

### Reading rows as dictionaries

```python
import csv

with open("students.csv", newline="", encoding="utf-8") as f:
    reader = csv.DictReader(f)
    for row in reader:
        print(row["name"], row["score"])
```

`DictReader` uses the header row for keys, so you write `row["score"]` instead of remembering that score is column 2.

Note that every value arrives as a **string**. Convert when you need numbers:

```python
total = 0
with open("students.csv", newline="", encoding="utf-8") as f:
    for row in csv.DictReader(f):
        total += float(row["score"])
print(total)
```

If you meet `could not convert string to float`, see our [string-to-float fix](https://articles.sythra.ai/articles/could-not-convert-string-to-float-fix).

### Writing rows

```python
import csv

rows = [
    {"name": "Ada", "score": 92},
    {"name": "Grace", "score": 88},
]

with open("out.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.DictWriter(f, fieldnames=["name", "score"])
    writer.writeheader()
    writer.writerows(rows)
```

### Why `newline=""`?

The `csv` module manages line endings itself. Without `newline=""`, files written on Windows can end up with a blank line between every row. Always include it for CSV.

## When to reach for pandas instead

For small, simple files the standard library is enough. Once you want filtering, grouping, missing-value handling or hundreds of thousands of rows, `pandas.read_csv("file.csv")` is shorter and faster. See [cleaning a messy CSV with pandas](https://articles.sythra.ai/articles/clean-messy-csv-pandas).

## A tiny practical example

Read a JSON list of tasks, mark one as done, save it back:

```python
import json
from pathlib import Path

path = Path("tasks.json")

tasks = json.loads(path.read_text(encoding="utf-8")) if path.exists() else []

tasks.append({"title": "Write article", "done": False})
tasks[0]["done"] = True

path.write_text(json.dumps(tasks, indent=2), encoding="utf-8")
print(f"{len(tasks)} task(s) saved")
```

Check-then-write is the shape of most small tools: load, change, save.

## Common errors

| Error | Usual cause | Fix |
|---|---|---|
| `FileNotFoundError` | Wrong path or wrong working folder | Print `Path.cwd()` and use an absolute path |
| `UnicodeDecodeError` | File is not in the encoding Python guessed | Pass `encoding="utf-8"` (or the file's real encoding) |
| `PermissionError` | File is open in Excel or another program | Close it, then retry |
| `JSONDecodeError` | Empty file, trailing comma, single quotes | Validate the file; JSON requires double quotes |

## FAQ

### What is the best way to read a file in Python?

Use `Path.read_text(encoding="utf-8")` for small files or `with open(path, encoding="utf-8") as f:` and loop over `f` for large ones. The `with` block closes the file automatically.

### How do I save a Python dictionary to a JSON file?

Open the file in write mode and call `json.dump(data, f, indent=2)`. Load it back later with `json.load(f)`.

### Why does my CSV have blank lines between rows on Windows?

You forgot `newline=""` when opening the file for writing. Pass `newline=""` to `open()` whenever you use the `csv` module.

### Should I use pathlib or os.path?

Prefer `pathlib` for new code. It reads more clearly and handles path separators for you on every operating system.

## Next reading on Sythra Articles

- [Stop renaming files by hand](https://articles.sythra.ai/articles/python-rename-files-script)
- [Clean a messy CSV with pandas](https://articles.sythra.ai/articles/clean-messy-csv-pandas)
- [Python list comprehensions: when to use them (and not)](https://articles.sythra.ai/articles/python-list-comprehensions)

## Glossary (terms defined in this article)
- **pathlib** (basic) — A built-in module that represents file paths as objects instead of plain strings
- **JSON** (basic) — A text format for structured data using objects, lists, strings and numbers; the common language of web APIs
