Working with JSON in Python | Python Course | Dataplexa

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 / null map to Python True / 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
<class 'dict'> laptop 999.99 True Alice admin <class 'list'> val
  • Raises json.JSONDecodeError if the string is not valid JSON — always wrap in try/except when 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))
{"name": "Bob", "age": 25, "active": true, "score": null, "tags": ["sql", "excel"], "coords": [48.8, 2.3]} <class 'str'> { "name": "Bob", "age": 25, "active": true, "score": null, "tags": [ "sql", "excel" ], "coords": [ 48.8, 2.3 ] } { "active": true, "age": 25, "coords": [ 48.8, 2.3 ], "name": "Bob", "score": null, "tags": [ "sql", "excel" ] } Compact length: 87 {"city": "São Paulo", "greeting": "こんにちは"}

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}")
Written to orders.json Total orders: 3 First item : notebook Order total : $109.47
  • Always pass encoding="utf-8" to avoid encoding issues across platforms.
  • Always use a with block — it closes the file automatically even if an error occurs.
  • Memory tip: load/loads reads; dump/dumps writes. The s means "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))
{"title": "Launch", "date": "2024-09-01", "attendees": [1, 2, 3], "price": 49.99} { "title": "Launch", "date": "2024-09-01", "attendees": [1, 2, 3], "price": 49.99 }

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}")
{'name': 'Alice'} JSON error: Expecting '}' delimiter: line 1 column 17 (char 16) None Name: Bob, Email: not provided

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")
Page 1 — 6 total users Active users with score ≥ 80: 3 #5 Eve [admin ] score=99 #1 Alice [admin ] score=92 #3 Carol [editor ] score=85 Saved to top_users.json

Quick Reference Table

FunctionDirectionWorks With
json.loads(s)JSON string → Python objectStrings (API responses, raw text)
json.dumps(obj)Python object → JSON stringStrings (send to API, store as text)
json.load(f)JSON file → Python objectFile objects (config files, datasets)
json.dump(obj, f)Python object → JSON fileFile objects (saving data to disk)
JSON TypePython TypeNotes
object {}dictKeys must be strings in JSON
array []listTuples also serialize to arrays
string ""strDouble quotes only
numberint / floatNo Decimal natively
true / falseTrue / FalseCase-sensitive in JSON
nullNone

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?





NEXT UP
Date & Time in Python
Learn to create, format, parse, and do arithmetic with dates and times using Python's datetime module.