
Python Course
Working with JSON in Python
JSON — JavaScript Object Notation — is the universal language of data exchange on the web. Every major API, configuration file, and data service you will encounter as a Python developer speaks JSON. Python's built-in json module makes reading, writing, and transforming JSON data straightforward and reliable, with no installation needed.
This lesson covers the four core functions, type mapping, formatting options, handling non-serializable types, deep extraction patterns, error handling, and a real-world API response example.
What JSON Looks Like
JSON is built from two structures — objects (key-value pairs in curly braces) and arrays (ordered lists in square brackets). If you know Python dictionaries and lists, JSON will feel immediately familiar.
# JSON maps almost 1:1 to Python data structures
sample_json = '''
{
"name": "Alice",
"age": 30,
"active": true,
"score": 98.5,
"tags": ["python", "data", "ml"],
"address": {
"city": "Austin",
"state": "TX"
},
"nickname": null
}
'''
# JSON → Python
# true → True
# false → False
# null → None
# {...} → dict
# [...] → list- JSON strings always use double quotes — single quotes are not valid JSON.
- JSON
true/false/nullmap to PythonTrue/False/None. - JSON only supports strings, numbers, booleans, null, objects, and arrays — no dates, sets, or tuples natively.
- Tuples serialize to JSON arrays and round-trip back as lists.
Parsing JSON Strings — json.loads()
json.loads() converts a JSON string into a Python object. The s stands for "string" — the key distinction from json.load() which reads from a file. This is the function you call on every API response body.
import json
# Flat object
raw = '{"product": "laptop", "price": 999.99, "in_stock": true}'
data = json.loads(raw)
print(type(data)) # <class 'dict'>
print(data["product"]) # laptop
print(data["price"]) # 999.99
print(data["in_stock"]) # True — Python bool, not the string "true"
# Nested access works exactly like a normal dict
response = '{"user": {"id": 42, "name": "Alice", "roles": ["admin", "editor"]}, "status": "ok"}'
obj = json.loads(response)
print(obj["user"]["name"]) # Alice
print(obj["user"]["roles"][0]) # admin
# JSON array at the top level
raw_list = '[1, 2, 3, {"key": "val"}]'
result = json.loads(raw_list)
print(type(result)) # <class 'list'>
print(result[3]["key"]) # val- Raises
json.JSONDecodeErrorif the string is not valid JSON — always wrap intry/exceptwhen parsing untrusted input. - The top-level JSON value can be an object, array, string, number, boolean, or null.
Serializing to JSON — json.dumps()
json.dumps() converts a Python object into a JSON string. Use this when you need to send data to an API, store it as text, or build an HTTP response body.
import json
user = {
"name": "Bob",
"age": 25,
"active": True, # Python True → JSON true
"score": None, # Python None → JSON null
"tags": ["sql", "excel"],
"coords": (48.8, 2.3) # tuple → JSON array (becomes list when parsed back)
}
# Compact — one line
raw = json.dumps(user)
print(raw)
print(type(raw)) # <class 'str'>
# Pretty — human-readable
pretty = json.dumps(user, indent=4)
print(pretty)
# Sort keys — consistent output for testing and version control diffs
print(json.dumps(user, sort_keys=True, indent=2))
# Compact separators — smallest possible payload (no spaces at all)
compact = json.dumps(user, separators=(",", ":"))
print("Compact length:", len(compact))
# Preserve non-ASCII characters instead of escaping to \uXXXX
intl = {"city": "São Paulo", "greeting": "こんにちは"}
print(json.dumps(intl, ensure_ascii=False))Reading and Writing JSON Files
json.load() reads directly from a file object. json.dump() writes directly to a file object. No need to read the whole file as a string first — these handle it in one step.
import json
# --- WRITE: json.dump() ---
orders = [
{"id": 1, "item": "notebook", "price": 4.99, "qty": 3},
{"id": 2, "item": "pen", "price": 1.50, "qty": 10},
{"id": 3, "item": "desk", "price": 89.99, "qty": 1},
]
with open("orders.json", "w", encoding="utf-8") as f:
json.dump(orders, f, indent=2)
print("Written to orders.json")
# --- READ: json.load() ---
with open("orders.json", "r", encoding="utf-8") as f:
loaded = json.load(f)
print("Total orders:", len(loaded))
print("First item :", loaded[0]["item"])
total = sum(item["price"] * item["qty"] for item in loaded)
print(f"Order total : ${total:.2f}")- Always pass
encoding="utf-8"to avoid encoding issues across platforms. - Always use a
withblock — it closes the file automatically even if an error occurs. - Memory tip:
load/loadsreads;dump/dumpswrites. Thesmeans "string".
Handling Non-Serializable Types
Not every Python object serializes to JSON automatically. Dates, sets, Decimal numbers, and custom class instances raise a TypeError by default. You have two approaches.
import json
from datetime import date, datetime
from decimal import Decimal
# Option 1 — convert manually before dumping
event = {
"title": "Launch",
"date": str(date(2024, 9, 1)), # date → "2024-09-01"
"attendees": list({1, 2, 3}), # set → list
"price": float(Decimal("49.99")), # Decimal → float
}
print(json.dumps(event))
# Option 2 — custom default function (cleaner for repeated use)
def json_default(obj):
if isinstance(obj, (date, datetime)):
return obj.isoformat()
if isinstance(obj, Decimal):
return float(obj)
if isinstance(obj, set):
return sorted(obj) # sorted for consistent output
raise TypeError(f"Type {type(obj).__name__} not JSON serializable")
event2 = {
"title": "Launch",
"date": date(2024, 9, 1),
"attendees": {3, 1, 2},
"price": Decimal("49.99"),
}
print(json.dumps(event2, default=json_default, indent=2))Safe Parsing — Error Handling
In production code you should never trust external JSON blindly. Wrap parsing in try/except and use .get() for optional keys.
import json
def safe_parse(raw):
"""Parse JSON string — return None on failure."""
try:
return json.loads(raw)
except json.JSONDecodeError as e:
print(f"JSON error: {e}")
return None
# Valid JSON
print(safe_parse('{"name": "Alice"}'))
# Invalid JSON — missing closing brace
print(safe_parse('{"name": "Alice"'))
# Safe key access — .get() with a default avoids KeyError
data = {"user": {"name": "Bob"}}
name = data.get("user", {}).get("name", "Unknown")
email = data.get("user", {}).get("email", "not provided")
print(f"Name: {name}, Email: {email}")Real-World Example — API Response Processing
This simulates receiving a paginated API response, extracting relevant fields, filtering, and writing the results to a clean JSON file.
import json
# Simulated API response (as a JSON string, the way it arrives over HTTP)
api_response = '''
{
"page": 1,
"total": 6,
"users": [
{"id": 1, "name": "Alice", "role": "admin", "active": true, "score": 92},
{"id": 2, "name": "Bob", "role": "user", "active": false, "score": 78},
{"id": 3, "name": "Carol", "role": "editor", "active": true, "score": 85},
{"id": 4, "name": "Dave", "role": "user", "active": true, "score": 55},
{"id": 5, "name": "Eve", "role": "admin", "active": true, "score": 99},
{"id": 6, "name": "Frank", "role": "user", "active": false, "score": 60}
]
}
'''
payload = json.loads(api_response)
# Extract only active users with score >= 80
active_top = [
{"id": u["id"], "name": u["name"], "role": u["role"], "score": u["score"]}
for u in payload["users"]
if u["active"] and u["score"] >= 80
]
print(f"Page {payload['page']} — {payload['total']} total users")
print(f"Active users with score ≥ 80: {len(active_top)}")
print()
for u in sorted(active_top, key=lambda x: x["score"], reverse=True):
print(f" #{u['id']} {u['name']:<10} [{u['role']:<8}] score={u['score']}")
# Save filtered results
with open("top_users.json", "w", encoding="utf-8") as f:
json.dump(active_top, f, indent=2)
print("\nSaved to top_users.json")Quick Reference Table
| Function | Direction | Works With |
|---|---|---|
json.loads(s) | JSON string → Python object | Strings (API responses, raw text) |
json.dumps(obj) | Python object → JSON string | Strings (send to API, store as text) |
json.load(f) | JSON file → Python object | File objects (config files, datasets) |
json.dump(obj, f) | Python object → JSON file | File objects (saving data to disk) |
| JSON Type | Python Type | Notes |
|---|---|---|
object {} | dict | Keys must be strings in JSON |
array [] | list | Tuples also serialize to arrays |
string "" | str | Double quotes only |
number | int / float | No Decimal natively |
true / false | True / False | Case-sensitive in JSON |
null | None |
Practice
Which function converts a JSON string into a Python object?
What Python value does JSON null map to?
Which json.dumps() parameter adds indentation for human-readable output?
What is the key difference between json.loads() and json.load()?
What exception does json.loads() raise when given invalid JSON?
What json.dumps() parameter accepts a function to handle non-serializable types?
Quick Quiz
What does json.dumps({"active": True, "score": None}) produce?
Which Python type cannot be serialized by json.dumps() without extra handling?
What does json.dumps(data, sort_keys=True) do?
If a JSON file contains an array at the top level, what Python type does json.load() return?
What does the default parameter in json.dumps() do?
Which json.dumps() option preserves Unicode characters like accented letters instead of escaping them?
datetime module.