Python JSON
Learn how to use Python's built-in json module to encode, decode, read, and write JSON data, with practical examples and error handling.
Python's built-in json module lets you convert between Python objects and JSON text with just two functions in most cases. This chapter covers everything you need: data-type mapping, dumps/loads for strings, dump/load for files, pretty-printing, error handling, and custom serialization.
What is JSON?
JSON (JavaScript Object Notation) is a lightweight, text-based data-interchange format. It is human-readable, language-independent, and the default format for most web APIs.
A JSON document is built from two structures:
- Objects — unordered collections of key/value pairs enclosed in
{}. Keys are always strings in double quotes. - Arrays — ordered sequences of values enclosed in
[].
Example JSON document:
{
"name": "John Doe",
"age": 30,
"city": "New York",
"hobbies": ["reading", "traveling", "photography"],
"active": true,
"score": null
}Python–JSON type mapping
When you encode or decode JSON, Python's json module converts types according to this table:
| Python type | JSON type | Python type (after decode) |
|---|---|---|
dict | object {} | dict |
list, tuple | array [] | list |
str | string "" | str |
int, float | number | int or float |
True / False | true / false | bool |
None | null | None |
Note that tuples become JSON arrays and come back as Python lists after decoding.
Encoding Python objects to JSON strings
json.dumps() (dump-string) serializes a Python object into a JSON-formatted string.
Basic encoding
Output:
{"name": "John Doe", "age": 30, "city": "New York", "hobbies": ["reading", "traveling", "photography"]}Pretty-printing with indent and sort_keys
By default json.dumps() produces a compact single-line string. Pass indent to produce human-readable output, and sort_keys=True to sort dictionary keys alphabetically:
import json
person = {
"name": "John Doe",
"age": 30,
"city": "New York",
"hobbies": ["reading", "traveling", "photography"]
}
print(json.dumps(person, indent=2, sort_keys=True))Output:
{
"age": 30,
"city": "New York",
"hobbies": [
"reading",
"traveling",
"photography"
],
"name": "John Doe"
}Use indent=2 or indent=4 when writing configuration files or debugging API responses — it makes nested structures easy to read.
Decoding JSON strings to Python objects
json.loads() (load-string) parses a JSON string and returns the equivalent Python object.
Basic decoding
Output:
{'name': 'John Doe', 'age': 30, 'city': 'New York', 'hobbies': ['reading', 'traveling', 'photography']}
<class 'dict'>
John DoeThe decoded value is a regular Python dictionary, so you can access its keys with [] or .get(), iterate it with for, and so on.
Handling malformed JSON
If the input is not valid JSON, json.loads() raises json.JSONDecodeError. Always catch it when working with data from external sources:
import json
raw = '{"name": "Alice", "age":}' # invalid — missing value
try:
data = json.loads(raw)
except json.JSONDecodeError as e:
print(f"Invalid JSON: {e}")Output:
Invalid JSON: Expecting value: line 1 column 24 (char 23)See the Python try/except chapter for a full guide to exception handling.
Working with nested JSON
JSON objects can contain other objects and arrays to any depth. Access nested values using chained [] notation:
Output:
John
travelingFor deeply nested structures, consider using .get() with a default to avoid KeyError on missing keys:
city = person.get("address", {}).get("city", "unknown")The nested dictionaries chapter covers patterns for working with deeply nested Python dicts.
Reading and writing JSON files
json.dump() writes to a file object, and json.load() reads from one. These are the file counterparts of dumps/loads.
Writing JSON to a file
import json
data = {"name": "Alice", "scores": [95, 87, 92]}
with open("data.json", "w") as f:
json.dump(data, f, indent=2)This creates data.json with pretty-printed content. Using with open(...) ensures the file is closed automatically — see Python file handling for details.
Reading JSON from a file
import json
with open("data.json") as f:
data = json.load(f)
print(data)
print(data["scores"])Output (assuming the file written above):
{'name': 'Alice', 'scores': [95, 87, 92]}
[95, 87, 92]Full round-trip example
import json
# Write
config = {"host": "localhost", "port": 5432, "debug": False}
with open("config.json", "w") as f:
json.dump(config, f, indent=2)
# Read back
with open("config.json") as f:
loaded = json.load(f)
print(loaded["port"]) # 5432
print(type(loaded["port"])) # <class 'int'>Fetching JSON from a web API
In most projects you'll decode JSON that comes from an HTTP response. The popular requests library makes this straightforward:
import requests
response = requests.get("https://jsonplaceholder.typicode.com/todos/1")
response.raise_for_status() # raises an error for 4xx/5xx responses
data = response.json() # equivalent to json.loads(response.text)
print(data["title"])
print(data["completed"])response.json() calls json.loads() internally. Always call raise_for_status() before parsing so a failed request doesn't silently return an error body.
Custom serialization with default
The json module cannot encode arbitrary Python objects (like datetime.date) by default. Pass a default callable or a custom JSONEncoder subclass to handle them:
import json
import datetime
class DateEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime.date):
return obj.isoformat()
return super().default(obj)
event = {"name": "Conference", "date": datetime.date(2024, 1, 15)}
print(json.dumps(event, cls=DateEncoder))Output:
{"name": "Conference", "date": "2024-01-15"}The date object is converted to its ISO 8601 string before serialization.
Custom deserialization with object_hook
object_hook is called for every JSON object (dict) as it is decoded. You can use it to transform data on the fly — for example, converting string values to the right Python types:
import json
def as_record(d):
"""Convert age field from string to int if present."""
if "age" in d:
d["age"] = int(d["age"])
return d
raw = '{"name": "Bob", "age": "25"}'
person = json.loads(raw, object_hook=as_record)
print(person)
print(type(person["age"])) # <class 'int'>Output:
{'name': 'Bob', 'age': 25}
<class 'int'>Quick reference
| Function | Direction | Source/target |
|---|---|---|
json.dumps(obj) | Python → JSON | returns a str |
json.loads(s) | JSON → Python | reads from a str |
json.dump(obj, f) | Python → JSON | writes to a file |
json.load(f) | JSON → Python | reads from a file |
Key optional parameters:
indent=2— pretty-print with 2-space indentationsort_keys=True— sort dictionary keys alphabeticallycls=MyEncoder— use a customJSONEncodersubclassobject_hook=fn— transform each decoded object dict