Python with AI Tutorial · Chapter 28 of 48
Python JSON handling lives in the built-in json module. JSON (JavaScript Object Notation) is the text format that web APIs, config files and AI services use to exchange data, and it maps almost one-to-one onto Python dictionaries and lists. Four functions do the work: loads and dumps for strings, load and dump for files.
You will meet JSON constantly from here on: every response in the requests chapter and every reply from an AI API arrives as JSON. Learning to read it into Python objects, walk through nested levels and write it back cleanly is the skill that unlocks all of that.
Parsing a JSON string with json.loads
json.loads (load from string) turns JSON text into Python objects. Objects become dictionaries, arrays become lists, and true, false and null become True, False and None.
import json
raw = '{"invoice": "INV-104", "amount": 12500.5, "paid": false, "items": ["Laptop", "Mouse"], "notes": null}'
data = json.loads(raw)
print(type(data))
print(data["invoice"], data["amount"])
print(data["paid"], data["notes"])
print(data["items"][0])
Output: <class 'dict'> INV-104 12500.5 False None Laptop
| JSON | Python (after loads) | Python (before dumps) |
|---|---|---|
object {} |
dict | dict |
array [] |
list | list, tuple |
| string | str | str |
| number (integer) | int | int |
| number (real) | float | float |
true / false |
True / False | True / False |
null |
None | None |
Notice the gaps: JSON has no date, Decimal, set or tuple type. Tuples silently become lists on the way out, and the others raise an error unless you tell dumps how to convert them, which is covered below.
Creating JSON with json.dumps
json.dumps (dump to string) goes the other way. Use indent for human-readable output and sort_keys=True when you want stable ordering for diffs or version control.
import json
employee = {
"name": "Priya Sharma",
"dept": "Finance",
"salary": 85000,
"skills": ("Excel", "Python"),
"manager": None,
"remote": True,
}
print(json.dumps(employee))
print(json.dumps(employee, indent=2, sort_keys=True))
Output:
{"name": "Priya Sharma", "dept": "Finance", "salary": 85000, "skills": ["Excel", "Python"], "manager": null, "remote": true}
{
"dept": "Finance",
"manager": null,
"name": "Priya Sharma",
"remote": true,
"salary": 85000,
"skills": [
"Excel",
"Python"
]
}
dumps and loads work with strings; dump and load work with open files. Calling json.dump(data) without a file object, or json.load("text") with a string, are the two most common errors in this module.Saving and reading JSON files
For files, open them in text mode with UTF-8 encoding and pass the file object to json.dump or json.load. The with block closes the file even if something fails. pathlib.Path keeps the path portable: write Path("data") / "orders.json" and Python inserts a backslash on Windows and a forward slash on Mac and Linux.
import json
from pathlib import Path
orders = [
{"order_id": 1001, "customer": "Acme Ltd", "total": 4599.0},
{"order_id": 1002, "customer": "Zen Foods", "total": 1250.75},
]
path = Path("orders.json")
with open(path, "w", encoding="utf-8") as f:
json.dump(orders, f, indent=2)
with open(path, encoding="utf-8") as f:
loaded = json.load(f)
print(len(loaded), "orders loaded")
print("Grand total:", sum(o["total"] for o in loaded))
print(loaded[1]["customer"])
Output: 2 orders loaded Grand total: 5849.75 Zen Foods
Walking through nested JSON
Real API responses nest objects inside arrays inside objects. Chain square brackets to drill down, loop over lists, and use .get() with a default for keys that might be missing so one absent field does not crash the whole report.
response = {
"status": "ok",
"data": {
"company": "NeoTech Traders",
"quarters": [
{"q": "Q1", "revenue": 420000, "regions": {"North": 250000, "South": 170000}},
{"q": "Q2", "revenue": 465000, "regions": {"North": 260000, "South": 205000}},
],
},
}
print(response["data"]["company"])
for q in response["data"]["quarters"]:
print(q["q"], q["revenue"], "South:", q["regions"]["South"])
best = max(response["data"]["quarters"], key=lambda q: q["revenue"])
print("Best quarter:", best["q"])
print("Currency:", response["data"].get("currency", "INR"))
Output: NeoTech Traders Q1 420000 South: 170000 Q2 465000 South: 205000 Best quarter: Q2 Currency: INR
When a response is deep and unfamiliar, print it once with json.dumps(response, indent=2) and read the structure before writing any lookups. Two minutes of looking saves twenty minutes of KeyError chasing.
Dates, Decimal and other unsupported types
Business data is full of dates and exact money values, and dumps refuses both by default. Pass a function to the default parameter; it is called only for values the encoder cannot handle, and whatever it returns is serialised instead.
import json
from datetime import date
from decimal import Decimal
invoice = {"no": "INV-104", "date": date(2026, 3, 31), "amount": Decimal("12500.50")}
try:
json.dumps(invoice)
except TypeError as e:
print("Error:", e)
def to_json(value):
if isinstance(value, date):
return value.isoformat()
if isinstance(value, Decimal):
return str(value)
raise TypeError(f"Cannot serialise {type(value).__name__}")
print(json.dumps(invoice, default=to_json))
Output:
Error: Object of type date is not JSON serializable
{"no": "INV-104", "date": "2026-03-31", "amount": "12500.50"}
Storing money as a string keeps every digit intact. On the way back in, rebuild it with Decimal(data["amount"]) and parse the date with date.fromisoformat.
A cleaner alternative for larger projects is to subclass json.JSONEncoder and override its default method, then pass cls=MyEncoder to dumps. The function approach shown here is enough for scripts and is easier to read when you come back to it months later.
Handling invalid JSON
Files get truncated, people hand-edit configs and add trailing commas, and APIs occasionally return an HTML error page instead of JSON. Catch json.JSONDecodeError and report the position so the problem can be fixed quickly.
import json
bad = '{"name": "Acme", "total": 4599,}'
try:
json.loads(bad)
except json.JSONDecodeError as e:
print("Invalid JSON:", e.msg)
print("Line", e.lineno, "column", e.colno)
Output: Invalid JSON: Expecting property name enclosed in double quotes Line 1 column 32
Updating a settings file and keeping non-ASCII text
A very common pattern is read, modify, write back. By default dumps escapes anything outside ASCII, so a rupee sign becomes \u20b9. Pass ensure_ascii=False to keep the real characters in the file; the data is identical either way once loaded.
import json
from pathlib import Path
settings_path = Path("settings.json")
settings_path.write_text(json.dumps({"currency": "INR", "gst_rate": 0.18}), encoding="utf-8")
settings = json.loads(settings_path.read_text(encoding="utf-8"))
settings["gst_rate"] = 0.12
settings["symbol"] = "₹"
print(json.dumps(settings))
settings_path.write_text(json.dumps(settings, indent=2, ensure_ascii=False), encoding="utf-8")
print(settings_path.read_text(encoding="utf-8"))
Output:
{"currency": "INR", "gst_rate": 0.12, "symbol": "\u20b9"}
{
"currency": "INR",
"gst_rate": 0.12,
"symbol": "₹"
}
Paste a real API response and ask for the exact lookup code.
Here is a JSON response from a weather API (pasted below). Write Python that loads it with the json module and prints the city name, today's max temperature and a list of the next 3 days' rain probability. Use .get() with sensible defaults so missing keys never raise KeyError, and add a comment explaining each nested level you access.
Get a reusable converter between Excel rows and JSON.
Write a Python function rows_to_json(rows, path) that takes a list of dicts exported from an Excel sheet (keys: invoice_no, invoice_date as datetime.date, customer, amount as Decimal) and saves them as pretty-printed UTF-8 JSON, converting dates to ISO strings and Decimals to strings. Then write json_to_rows(path) that reverses it exactly, restoring date and Decimal types. Include a round-trip test.
Common mistakes
- Using
json.loadon a string orjson.loadson a file object. Thesmeans string. - Writing Python literals by hand, such as
{'paid': True}with single quotes; JSON requires double quotes and lowercasetrue. - Forgetting
encoding="utf-8"when opening files on Windows, which corrupts accented names and currency symbols. - Trying to serialise
datetimeorDecimalwithout adefaultfunction. - Storing money as JSON floats. Use strings and convert back to
Decimal.
Exercise
Given the orders list from the file example, add a new order {"order_id": 1003, "customer": "Bright Labs", "total": 980.0}, save all three to orders.json, then read the file back and print the customer with the highest total.
Show answer
import json
from pathlib import Path
orders = [
{"order_id": 1001, "customer": "Acme Ltd", "total": 4599.0},
{"order_id": 1002, "customer": "Zen Foods", "total": 1250.75},
]
orders.append({"order_id": 1003, "customer": "Bright Labs", "total": 980.0})
path = Path("orders.json")
with open(path, "w", encoding="utf-8") as f:
json.dump(orders, f, indent=2)
with open(path, encoding="utf-8") as f:
data = json.load(f)
top = max(data, key=lambda o: o["total"])
print(top["customer"]) # Acme Ltd
Related chapters
FAQ
What is the difference between json.dump and json.dumps?
json.dumps returns the JSON as a string. json.dump writes it straight into an open file object. The same s rule applies to json.loads (string) and json.load (file).
How do I convert a Python dictionary to JSON?
Call json.dumps(my_dict). Add indent=2 for readable output and default=str if the dictionary contains dates or Decimals that JSON cannot represent.
Does JSON keep the order of dictionary keys?
Yes in practice. Python 3.7+ dictionaries preserve insertion order and json.dumps writes keys in that order unless you pass sort_keys=True.
Working with spreadsheets too? Ready-made Excel, Google Sheets and Power BI templates are at NextGenTemplates.com.
Chapter 28 of 48 · Python with AI: all 48 chapters



