Read and Write JSON Files in Python (Beginner Guide)

Posted November 17, 2025 by Karol Polakowski

JSON is the most common format for exchanging structured data in web apps and tools. In Python you’ll typically use the built-in json module for most tasks; this guide walks through practical examples for reading, writing, updating, and handling common pitfalls.


How JSON works in Python

Python’s standard library includes the json module. The two core pairs you’ll use are:

  • json.load(file) / json.loads(string) — parse JSON to Python objects (dict, list, str, int, float, bool, None)
  • json.dump(obj, file) / json.dumps(obj) — serialize Python objects to JSON

JSON maps closely to Python types:

  • object -> dict
  • array -> list
  • string -> str
  • number -> int/float
  • true/false -> True/False
  • null -> None

Reading JSON files

Use a context manager to open files safely. json.load parses from a file-like object.

import json
from pathlib import Path

path = Path("data.json")
with path.open("r", encoding="utf-8") as f:
    data = json.load(f)

# data is now a Python dict or list
print(type(data))
print(data)

If you already have a JSON string, use json.loads:

import json
s = '{"name": "Alice", "age": 30}'
obj = json.loads(s)
print(obj["name"])  # Alice

Error handling: catch json.JSONDecodeError to handle malformed input.

import json
from json import JSONDecodeError

try:
    with open("broken.json", "r", encoding="utf-8") as f:
        json.load(f)
except JSONDecodeError as exc:
    print("Bad JSON:", exc)

For very large files that won’t fit in memory, consider streaming parsers like ijson (third-party) or store newline-delimited JSON (JSONL) and parse line-by-line.

Writing JSON files

The simplest write uses json.dump. Use indent to pretty-print and ensure_ascii=False when you want UTF-8 output with non-ASCII characters preserved.

import json
from pathlib import Path

path = Path("data.json")
obj = {"name": "Zoë", "roles": ["dev", "reviewer"]}

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

If you prefer to build the string first:

import json
s = json.dumps(obj, indent=2, ensure_ascii=False)
with open("data.json", "w", encoding="utf-8") as f:
    f.write(s)

Atomic writes (safe overwrites)

To avoid corrupting a file if your program crashes while writing, write to a temporary file and then replace the original atomically.

import json
import tempfile
import os
from pathlib import Path

path = Path("data.json")
obj = {"counter": 1}

with tempfile.NamedTemporaryFile("w", delete=False, encoding="utf-8") as tmp:
    json.dump(obj, tmp, indent=2, ensure_ascii=False)
    tmp_path = tmp.name

# os.replace is atomic on POSIX and Windows
os.replace(tmp_path, str(path))

Appending and JSON Lines (JSONL)

Appending to a standard JSON array in a file requires reading, modifying, and rewriting the file. For logs or append-heavy workloads prefer JSON Lines (one JSON object per line).

Example JSONL write (append mode):

import json
from pathlib import Path

path = Path("events.jsonl")
event = {"type": "visit", "user": "alice"}
with path.open("a", encoding="utf-8") as f:
    f.write(json.dumps(event, ensure_ascii=False) + "\n")

Read JSONL line-by-line:

import json
from pathlib import Path

path = Path("events.jsonl")
with path.open("r", encoding="utf-8") as f:
    for line in f:
        if line.strip():
            obj = json.loads(line)
            print(obj)

JSONL is great for logs, streaming consumption, and parallel processing.


Handling non-serializable objects

By default json.dumps fails on custom objects (datetime, Decimal, dataclasses, etc.). You can provide a default function to convert unknown types.

import json
from dataclasses import dataclass, asdict
from datetime import datetime

@dataclass
class Event:
    name: str
    ts: datetime

obj = Event("start", datetime.utcnow())

def default(o):
    if hasattr(o, "isoformat"):
        return o.isoformat()
    if hasattr(o, "__dict__"):
        return o.__dict__
    raise TypeError(f"Object of type {o.__class__.__name__} is not JSON serializable")

s = json.dumps(obj, default=default)
print(s)

For dataclasses you can use asdict(obj) before serializing.

Updating an existing JSON file

Common pattern: read, modify, write (prefer atomic write from earlier).

import json
from pathlib import Path

p = Path("config.json")
with p.open("r", encoding="utf-8") as f:
    cfg = json.load(f)

# Modify
cfg["feature_enabled"] = True

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

This is simple but not suitable for concurrent writers — consider external storage (DB, KV store) or file locking if multiple processes will write.

Streaming & large files

  • Use JSONL where possible for append/streaming workloads.
  • For very large JSON arrays, parse incrementally with ijson (pip install ijson) to avoid high memory usage.
  • If your file is a single gigantic JSON array, you can stream the file and incrementally feed a parser.

Example using ijson (conceptual):

# Requires: pip install ijson
import ijson

with open("big.json", "rb") as f:
    for item in ijson.items(f, "item"):
        process(item)

Best practices and tips

  • Always open files with explicit encoding=”utf-8″.
  • Use indent for files you will manually read; omit indent for compact storage or to save space.
  • Use ensure_ascii=False to keep Unicode human-readable; otherwise non-ASCII characters are escaped.
  • Validate important external JSON with a JSON schema (jsonschema library).
  • Avoid eval or ast.literal_eval for untrusted JSON — json loads are safe and standardized.
  • For concurrent writes, prefer a proper database, or implement file locking (fcntl on Unix, msvcrt on Windows) or use atomic replace patterns.
  • Prefer JSONL for logs and append-heavy output.

Troubleshooting common errors

  • JSONDecodeError: check for trailing commas, unquoted keys, or single quotes (JSON requires double quotes).
  • Unicode errors: ensure files are read/written with encoding=”utf-8″.
  • TypeError: Object not serializable — provide a default, convert to dict, or transform dataclass to asdict.

Quick cheatsheet

  • Read: with open("f.json") as f: data = json.load(f)
  • Write: with open("f.json", "w") as f: json.dump(obj, f, indent=2)
  • JSONL append: f.write(json.dumps(obj) + "\n")
  • Atomic write: write to temp file + os.replace(temp, target)

Following these patterns will cover most common JSON tasks in Python, from tiny scripts to production tools that need safe, efficient JSON handling.