To write JSON data to a file in Python, open the file in write mode and call json.dump(data, f). Add indent=2 for readable output, and open the file with encoding="utf-8" so names with accents survive:
with open("orders.json", "w", encoding="utf-8") as f:
json.dump(orders, f, indent=2, ensure_ascii=False)
These snippets ran on Python 3.12.5 in a Windows Command Prompt, where encoding mistakes show up fast.
You’ll see json.dump with a list of records, the ensure_ascii setting, the datetime TypeError and its fix, appending to a JSON file, and writing without corrupting the file if something fails.
Write JSON to a file with json.dump()
json.dump() writes straight to a file object; json.dumps() returns a string instead. Here a list of order dictionaries goes to disk and is read back:
import json
from pathlib import Path
orders = [
{"id": 1001, "customer": "Emily Carter", "city": "Austin", "total": 129.99, "paid": True},
{"id": 1002, "customer": "José Ramírez", "city": "San Antonio", "total": 54.5, "paid": False},
]
with open("orders.json", "w", encoding="utf-8") as f:
json.dump(orders, f, indent=2, ensure_ascii=False)
print(Path("orders.json").read_text(encoding="utf-8"))
print(json.load(open("orders.json", encoding="utf-8"))[1]["customer"])
Output:
[
{
"id": 1001,
"customer": "Emily Carter",
"city": "Austin",
"total": 129.99,
"paid": true
},
{
"id": 1002,
"customer": "José Ramírez",
"city": "San Antonio",
"total": 54.5,
"paid": false
}
]
José Ramírez

Python’s True becomes true and None becomes null. The mapping goes the other way too, as shown in converting a Python dict to JSON.
Save a Python dictionary as a JSON file and load it back
A single dict works the same way. The surprise comes when you read it back: JSON object keys are always strings, so integer keys don’t round-trip:
import json
from pathlib import Path
settings = {"store": "Austin #12", "tax_rate": 0.0825, "open_days": ["Mon", "Tue", "Wed"],
1001: "Emily Carter", 1002: "James Wilson"} # int keys
Path("settings.json").write_text(json.dumps(settings, indent=2, sort_keys=False), encoding="utf-8")
loaded = json.loads(Path("settings.json").read_text(encoding="utf-8"))
print(loaded == settings, list(loaded)[-2:]) # int keys came back as strings
fixed = {int(k) if k.isdigit() else k: v for k, v in loaded.items()}
print(fixed == settings)
try:
json.dumps({("TX", "Austin"): 12})
except TypeError as err:
print("TypeError:", err)
Output:
False ['1001', '1002']
True
TypeError: keys must be str, int, float, bool or None, not tuple

Tuple keys can’t be saved at all. Convert them to strings first, or store a list of records instead. Reading JSON text into a dict is covered in converting a JSON string to a dictionary.
Keep accented characters readable with ensure_ascii
By default, json escapes every non-ASCII character. That’s valid JSON, but hard to read:
import json
row = {"customer": "José Ramírez", "note": "Café ☕"}
print(json.dumps(row)) # default: ensure_ascii=True
print(json.dumps(row, ensure_ascii=False)) # readable, needs a UTF-8 file
print(json.dumps(row, separators=(",", ":"), ensure_ascii=False)) # compact
Output:
{"customer": "Jos\u00e9 Ram\u00edrez", "note": "Caf\u00e9 \u2615"}
{"customer": "José Ramírez", "note": "Café ☕"}
{"customer":"José Ramírez","note":"Café ☕"}
With ensure_ascii=False you must write the file as UTF-8, or Windows may fall back to another code page. For spacing and sorting options, see pretty printing JSON in Python.
Why do I get “Object of type datetime is not JSON serializable”?
JSON only knows strings, numbers, booleans, null, arrays and objects. A datetime, Decimal or set stops json.dump():
import json
from datetime import datetime
order = {"id": 1003, "placed": datetime(2026, 10, 6, 9, 30), "total": 89.0}
try:
with open("order.json", "w", encoding="utf-8") as f:
json.dump(order, f)
except TypeError as err:
print("TypeError:", err)
print("left behind:", repr(open("order.json", encoding="utf-8").read()))
Output:
TypeError: Object of type datetime is not JSON serializable
left behind: '{"id": 1003, "placed": '

Pass a default function. json calls it for anything it can’t handle, and you return something it can:
import json
from datetime import date, datetime
from decimal import Decimal
from pathlib import Path
def to_json(value):
if isinstance(value, (datetime, date)):
return value.isoformat()
if isinstance(value, Decimal):
return str(value) # keep every cent exactly
if isinstance(value, set):
return sorted(value)
raise TypeError(f"Can't serialize {type(value).__name__}")
order = {"id": 1003, "placed": datetime(2026, 10, 6, 9, 30), "ship_by": date(2026, 10, 9),
"total": Decimal("89.10"), "tags": {"gift", "express"}}
with open("order.json", "w", encoding="utf-8") as f:
json.dump(order, f, indent=2, default=to_json)
print(Path("order.json").read_text(encoding="utf-8"))
Output:
{
"id": 1003,
"placed": "2026-10-06T09:30:00",
"ship_by": "2026-10-09",
"total": "89.10",
"tags": [
"express",
"gift"
]
}

The failed run above left a partial order.json behind, since the file was opened before the error. The safe-write pattern below avoids that. Before storing timestamps, read naive vs aware datetime in Python so the ISO strings carry a time zone when they need one.
Append data to a JSON file
A JSON file holds one value, so you can’t just append to it. Either load it, change it and rewrite it, or switch to JSON Lines: one JSON object per line, which works with append mode:
import json
from pathlib import Path
Path("events.jsonl").unlink(missing_ok=True)
def log_event(event):
with open("events.jsonl", "a", encoding="utf-8") as f: # append one line per record
f.write(json.dumps(event) + "\n")
log_event({"user": "olivia", "action": "login", "at": "2026-10-06T08:01:00"})
log_event({"user": "james", "action": "checkout", "amount": 42.75})
log_event({"user": "olivia", "action": "logout"})
print(Path("events.jsonl").read_text(encoding="utf-8"))
events = [json.loads(line) for line in open("events.jsonl", encoding="utf-8")]
print(len(events), "events; checkouts:", sum(e.get("amount", 0) for e in events))
Output:
{"user": "olivia", "action": "login", "at": "2026-10-06T08:01:00"}
{"user": "james", "action": "checkout", "amount": 42.75}
{"user": "olivia", "action": "logout"}
3 events; checkouts: 42.75

I use JSON Lines for logs and exports that grow over time. If you’re writing other text formats, see writing a list to a file in Python.
Write a JSON file safely with os.replace()
If dump() fails halfway, the old file is already truncated. Write to a temporary file in the same folder, then swap it in with os.replace():
import json
import os
import tempfile
from pathlib import Path
def write_json_atomic(path, data):
path = Path(path)
fd, tmp = tempfile.mkstemp(dir=path.parent, suffix=".tmp")
try:
with os.fdopen(fd, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2)
f.flush()
os.fsync(f.fileno())
os.replace(tmp, path) # swap in one step
except BaseException:
os.remove(tmp)
raise
write_json_atomic("settings.json", {"theme": "dark", "currency": "USD"})
try:
write_json_atomic("settings.json", {"theme": "light", "bad": {1, 2}})
except TypeError as err:
print("write failed:", err)
print("file still holds:", Path("settings.json").read_text(encoding="utf-8").replace("\n", " "))
Output:
write failed: Object of type set is not JSON serializable
file still holds: { "theme": "dark", "currency": "USD" }
The second write fails on the set, and the original settings survive untouched. To replace an existing file on purpose, see overwriting a file in Python.
JSON writing options compared
| Need | Code |
|---|---|
| Write to a file | json.dump(data, f) |
| Get a string | json.dumps(data) |
| Readable file | indent=2, ensure_ascii=False + encoding="utf-8" |
| datetime / Decimal / set | default=to_json |
| Add records over time | JSON Lines with mode "a" |
| Never leave a broken file | Temp file + os.replace() |
Working with JSON regularly? These guides are next:
- Write a dictionary to a text file in Python
- Work with JSON data in Python
- Convert a dictionary to JSON in Python
- Convert a pandas DataFrame to JSON