Before this lesson
Understand dictionaries, JSON concepts and try/except. Use a separate practice directory.
What you will be able to do
Persist structured data with explicit encoding and resource handling, then validate the result.
Understand the mechanism
Relative paths use the working directory, which may differ from the script directory. pathlib models paths across systems. Writes have real effects, so exercises use isolated or temporary locations.
Specify text encoding and use context management for reliable closure. I/O failure and invalid JSON are different problems; existence alone does not guarantee valid structure.
JSON represents ordinary data rather than complete objects or functions. File-based and string-based APIs differ. Human-readable Unicode output still requires an explicit encoding.
Read back and validate fields as an end-to-end check. Production persistence may require replacement, backup and concurrency strategies; this temporary-directory example does not claim full crash recovery.
Runnable example
Use Python 3.11 or later. Save lesson.py in a separate practice directory and run python3 lesson.py, or py lesson.py on Windows. Examples use only the standard library.
import json
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as folder:
path = Path(folder) / "progress.json"
data = {"version": 1, "courses": ["Python", "数据库"], "done": 2}
with path.open("w", encoding="utf-8") as stream:
json.dump(data, stream, ensure_ascii=False, indent=2)
with path.open(encoding="utf-8") as stream:
loaded = json.load(stream)
if loaded.get("version") != 1 or type(loaded.get("done")) is not int:
raise ValueError("Unexpected progress format")
print(loaded["courses"][1], loaded["done"])
print(path.exists())
print("Temporary practice directory closed")
Trace and verify
Expect the Chinese course name, two and True, followed by cleanup. Try malformed JSON and then valid JSON with missing version to distinguish syntax from schema failures.
Common mistakes and boundaries
Successful parsing does not authenticate data. Large files may require streaming or a different format, and real fixed-path writes need an overwrite policy.
Practice challenge
Add a loader with a defined missing-file default and explicit corruption failure without overwriting the original.
Reference solution and reasoning
Use a narrow missing-file fallback and preserve parsing failures with context. Keeping the original helps diagnosis and recovery.
Lab: malformed JSON must not become an empty overwrite
Returning an empty array for every read failure can overwrite recoverable user data on the next save. Distinguish an absent file from an existing malformed one. This experiment uses an automatically cleaned temporary directory and never touches personal files.
import json
import os
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as folder:
target = Path(folder) / "progress.json"
target.write_text('{"unfinished":', encoding="utf-8")
before = target.read_bytes()
try:
json.loads(target.read_text(encoding="utf-8"))
except json.JSONDecodeError:
print("invalid JSON: original preserved")
assert target.read_bytes() == before
# A separate, explicitly chosen valid replacement:
replacement = {"version": 1, "items": []}
temporary = target.with_suffix(".tmp")
temporary.write_text(json.dumps(replacement), encoding="utf-8")
os.replace(temporary, target)
print(json.loads(target.read_text(encoding="utf-8"))["version"])
Expected observations
Output is invalid JSON: original preserved and then 1. The replacement is a separate explicit decision in the lab, not an automatic reset triggered by the exception handler.
Why this happens and where it stops applying
Writing a temporary file in the same directory before replacement reduces exposure to partial JSON. It does not by itself guarantee durability after power loss or prevent concurrent writers from overwriting each other. Backups, permissions, locking and filesystem semantics remain separate concerns. Successful parsing is also not schema validation: 42 is valid JSON but not this application document.
Extension challenge
Design separate messages for a first-run missing file, malformed content and a permission error. Which situation may initialize a new file?
Reasoned solution
Initialize only under the defined first-run policy when the target is genuinely absent. Preserve malformed content for recovery and explain permission failures separately. Do not collapse all three into an empty list with a broad except Exception.