Skip to content

8. Files, JSON & errors

Beginner · 9 min read

Loading documents, saving results and surviving bad input — the everyday plumbing of every project.

8.1 Reading and writing text files

from pathlib import Path

path = Path("notes.txt")

path.write_text("RAG = retrieval + generation\n", encoding="utf-8")   # create / overwrite
text = path.read_text(encoding="utf-8")                              # read it all back
print(text.strip())                       # → RAG = retrieval + generation

# The `with` block closes the file for you, even if an error happens.
with open(path, "a", encoding="utf-8") as f:   # "a" = append, "w" = overwrite, "r" = read
    f.write("Agents = LLM + tools\n")

with open(path, encoding="utf-8") as f:
    for line in f:                        # read line by line (memory-friendly for big files)
        print(line.strip())

Always pass encoding="utf-8" — Windows otherwise uses a different default and breaks on ₹, emoji or Hindi text.

8.2 Working with folders

from pathlib import Path

docs = Path("docs")
docs.mkdir(exist_ok=True)                         # create the folder if missing
(docs / "a.md").write_text("hello", encoding="utf-8")   # "/" joins paths on any OS

for file in sorted(docs.glob("*.md")):            # every .md file in the folder
    print(file.name, file.stat().st_size, "bytes")   # → a.md 5 bytes

8.3 JSON

import json

config = {"model": "gpt-4o-mini", "temperature": 0.2, "tags": ["rag", "demo"]}

text = json.dumps(config)                  # Python dict → JSON string
print(text)
# → {"model": "gpt-4o-mini", "temperature": 0.2, "tags": ["rag", "demo"]}

back = json.loads(text)                    # JSON string → Python dict
print(back["tags"][0])                     # → rag

with open("config.json", "w", encoding="utf-8") as f:
    json.dump(config, f, indent=2)         # write to a file, nicely indented
with open("config.json", encoding="utf-8") as f:
    print(json.load(f)["model"])           # → gpt-4o-mini

8.4 Handling errors: try / except

def parse_score(raw):
    try:
        return float(raw)                  # code that might fail
    except ValueError:                     # catch the specific error you expect
        print(f"Not a number: {raw!r}")
        return 0.0

print(parse_score("0.75"))                 # → 0.75
print(parse_score("high"))                 # prints a warning, then → 0.0
try:
    data = json.loads('{"broken": ')       # invalid JSON
except json.JSONDecodeError as err:        # `as err` gives you the error object
    print("Bad JSON at position", err.pos) # → Bad JSON at position 11
else:
    print("parsed fine")                   # runs only if there was NO error
finally:
    print("done")                          # always runs — cleanup goes here

Catch specific exceptions. A bare except: hides real bugs.

8.5 Raising your own errors

def chunk(text, size):
    if size <= 0:
        raise ValueError("size must be positive")   # stop with a clear message
    words = text.split()
    return [" ".join(words[i:i + size]) for i in range(0, len(words), size)]

print(chunk("a b c d e", 2))               # → ['a b', 'c d', 'e']

Why it matters for GenAI

Models are asked to return JSON constantly — and sometimes they return invalid JSON. Parsing inside try/except json.JSONDecodeError and retrying is a standard pattern.

Practice

  • Save a list of three chat messages to history.json, read it back and print the last message's content.
  • Write safe_int(text, default=0) that returns default when the text isn't a number.
Answer
def safe_int(text, default=0):
    try:
        return int(text)
    except ValueError:
        return default

print(safe_int("12"), safe_int("twelve"))   # → 12 0