Python Lesson 27 – JSON | Dataplexa

Working with JSON

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.

This lesson covers everything from basic parsing to working with files, handling edge cases, and formatting JSON for human readability.

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 looks almost identical to Python dicts and lists

sample_json = '''
{
    "name": "Alice",
    "age": 30,
    "active": true,
    "score": 98.5,
    "tags": ["python", "data", "ml"],
    "address": {
        "city": "Austin",
        "state": "TX"
    },
    "nickname": null
}
'''
# true  → True   in Python
# false → False  in Python
# null  → None   in Python
  • JSON strings always use double quotes — single quotes are not valid JSON
  • JSON true / false / null map directly to Python True / False / None
  • JSON objects map to Python dicts, JSON arrays map to Python lists
  • JSON only supports strings, numbers, booleans, null, objects, and arrays — no dates, sets, or tuples natively

Parsing JSON — json.loads()

json.loads() converts a JSON string into a Python object. The name stands for "load string" — the s is the key distinction from json.load() which reads from a file.

Why it exists: API responses arrive as raw text strings over HTTP. json.loads() turns that raw text into a native Python dictionary or list you can work with immediately.

Real-world use: a weather API returns a JSON string — you parse it and pull out the temperature, city name, and forecast with normal dictionary access.

# json.loads() — parse a JSON string into a Python object

import json

raw = '{"product": "laptop", "price": 999.99, "in_stock": true}'

data = json.loads(raw)   # string → Python dict

print(type(data))              # 
print(data["product"])         # laptop
print(data["price"])           # 999.99
print(data["in_stock"])        # True  (Python bool, not JSON true)

# Nested access works just like a normal dict
response = '{"user": {"id": 42, "name": "Alice"}, "status": "ok"}'
obj = json.loads(response)
print(obj["user"]["name"])     # Alice
<class 'dict'>
laptop
999.99
True
Alice
  • json.loads() takes a string and returns a Python dict, list, str, int, float, bool, or None
  • The top-level JSON value can be an object, array, string, number, boolean, or null
  • Raises json.JSONDecodeError if the string is not valid JSON

Serializing to JSON — json.dumps()

json.dumps() converts a Python object into a JSON string. The name stands for "dump string". This is what you use when you need to send data to an API or store it as text.

Real-world use: building an API response — you construct a Python dictionary with the results, then serialize it to a JSON string to send back to the client.

# json.dumps() — convert a Python object to a JSON string

import json

user = {
    "name": "Bob",
    "age": 25,
    "active": True,       # Python True → JSON true
    "score": None,        # Python None → JSON null
    "tags": ["sql", "excel"]
}

raw = json.dumps(user)
print(raw)
print(type(raw))   # 

# Pretty-print with indentation — much more readable
pretty = json.dumps(user, indent=4)
print(pretty)
{"name": "Bob", "age": 25, "active": true, "score": null, "tags": ["sql", "excel"]}
<class 'str'>
{
"name": "Bob",
"age": 25,
"active": true,
"score": null,
"tags": [
"sql",
"excel"
]
}
  • json.dumps() always returns a string — use json.dump() to write directly to a file
  • indent=4 adds human-readable formatting — use it for config files, debugging, and logs
  • sort_keys=True sorts dictionary keys alphabetically in the output
  • Python Truetrue, Falsefalse, Nonenull in the output

Reading JSON from a File — json.load()

json.load() reads directly from a file object — no need to read the file as a string first. This is the standard approach for loading JSON configuration files and datasets.

Real-world use: a Python application reads its settings from a config.json file at startup — database host, port, feature flags, and API keys all stored as structured JSON.

# json.load() — read JSON directly from a file

import json

# First, create a sample JSON file to read
sample = {"app": "dataplexa", "version": "2.1", "debug": False}
with open("config.json", "w") as f:
    json.dump(sample, f, indent=2)   # write JSON to file

# Now read it back
with open("config.json", "r") as f:
    config = json.load(f)            # file object → Python dict

print(config)
print("App:", config["app"])
print("Debug mode:", config["debug"])
{'app': 'dataplexa', 'version': '2.1', 'debug': False}
App: dataplexa
Debug mode: False
  • json.load(f) reads from a file object — the file must be open in text mode ("r")
  • json.dump(obj, f) writes to a file object — the file must be open in write mode ("w")
  • Always use a with block — it closes the file automatically even if an error occurs
  • Remember: loads / dumps work with strings; load / dump work with files

Writing JSON to a File — json.dump()

json.dump() serializes a Python object and writes it directly to a file in one step. Combine it with indent to produce clean, readable JSON files.

# json.dump() — write Python data to a JSON file

import json

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") as f:
    json.dump(orders, f, indent=4)   # write list of dicts as JSON array

print("orders.json written successfully.")

# Read it back to verify
with open("orders.json", "r") as f:
    loaded = json.load(f)
print("Total orders:", len(loaded))
print("First item:", loaded[0]["item"])
orders.json written successfully.
Total orders: 3
First item: notebook

Handling Non-Serializable Types

Not every Python object can be converted to JSON automatically. Dates, sets, custom class instances, and Decimal numbers will raise a TypeError by default. You have two options: convert them manually before serializing, or provide a custom encoder.

# Handling types that JSON cannot serialize by default

import json
from datetime import date

# Option 1 — convert manually before dumping
event = {
    "title": "Launch",
    "date": str(date(2024, 9, 1)),   # convert date to string manually
    "attendees": list({1, 2, 3})     # convert set to list manually
}
print(json.dumps(event))

# Option 2 — custom default function passed to json.dumps
def json_default(obj):
    if isinstance(obj, date):
        return obj.isoformat()       # "2024-09-01"
    raise TypeError(f"Type {type(obj)} not serializable")

event2 = {"title": "Launch", "date": date(2024, 9, 1)}
print(json.dumps(event2, default=json_default))
{"title": "Launch", "date": "2024-09-01", "attendees": [1, 2, 3]}
{"title": "Launch", "date": "2024-09-01"}
  • Types that are not JSON-serializable by default: datetime, date, set, tuple (becomes array), custom objects, Decimal
  • The default parameter accepts a function that handles unrecognized types
  • Tuples are silently converted to JSON arrays — they round-trip back as lists

Useful json.dumps() Options

A few extra keyword arguments make json.dumps() much more useful in production code.

# Useful json.dumps() keyword arguments

import json

data = {"z_key": 3, "a_key": 1, "m_key": 2}

# sort_keys — alphabetical key order (useful for consistency and diffs)
print(json.dumps(data, sort_keys=True))

# separators — compact output with no extra spaces (smaller payload)
print(json.dumps(data, separators=(",", ":")))

# ensure_ascii=False — preserve non-ASCII characters (e.g. accented letters)
intl = {"city": "São Paulo", "country": "Brasil"}
print(json.dumps(intl, ensure_ascii=False))
{"a_key": 1, "m_key": 2, "z_key": 3}
{"z_key":3,"a_key":1,"m_key":2}
{"city": "São Paulo", "country": "Brasil"}
  • sort_keys=True makes output deterministic — useful for testing and version control diffs
  • separators=(",", ":") removes all unnecessary spaces — smallest possible JSON string
  • ensure_ascii=False keeps Unicode characters as-is instead of escaping them to \uXXXX

Summary 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)

Practice Questions

Practice 1. Which function converts a JSON string into a Python object?



Practice 2. What Python value does JSON null map to?



Practice 3. Which json.dumps() parameter adds indentation for human-readable output?



Practice 4. What is the key difference between json.loads() and json.load()?



Practice 5. What exception does json.loads() raise when given invalid JSON?



Quiz

Quiz 1. What does json.dumps({"active": True, "score": None}) produce?






Quiz 2. Which of the following Python types cannot be serialized by json.dumps() without extra handling?






Quiz 3. What does json.dumps(data, sort_keys=True) do?






Quiz 4. If a JSON file contains an array at the top level, what Python type does json.load() return?






Quiz 5. What does the default parameter in json.dumps() do?






Next up — Date & Time: working with dates, times, and timedeltas using Python's datetime module.