JSON (JavaScript Object Notation) is a lightweight format for storing and exchanging structured data. Python provides built-in support through the json module.
JSON supports:
| JSON type | Python type |
|---|---|
Object {} |
dict |
Array [] |
list |
| String | str |
| Number | int or float |
true / false |
True / False |
null |
None |
Example JSON:
{
"name": "Alice",
"age": 30,
"skills": ["Python", "SQL"],
"active": true
}The equivalent Python object is:
{
"name": "Alice",
"age": 30,
"skills": ["Python", "SQL"],
"active": True
}Important differences:
- JSON uses lowercase
true,false, andnull. - JSON requires double quotes around strings and keys.
- JSON does not support comments.
- JSON keys must be strings.
import jsonPython provides two main pairs of functions:
json.load()andjson.dump()work with files.json.loads()andjson.dumps()work with strings.
Suppose user.json contains:
{
"name": "Alice",
"age": 30
}Read it with:
import json
with open("user.json", "r", encoding="utf-8") as file:
data = json.load(file)
print(data)
print(data["name"])Output:
{'name': 'Alice', 'age': 30}
Alice
It automatically closes the file, even if an error occurs.
import json
user = {
"name": "Alice",
"age": 30
}
with open("user.json", "w", encoding="utf-8") as file:
json.dump(user, file)The file will contain:
{"name": "Alice", "age": 30}with open("user.json", "w", encoding="utf-8") as file:
json.dump(user, file, indent=4)Output:
{
"name": "Alice",
"age": 30
}Useful options:
json.dump(
user,
file,
indent=4,
sort_keys=True
)indent=4: formats the JSON neatly.sort_keys=True: sorts dictionary keys alphabetically.
data = {"name": "Alice", "age": 30}
text = json.dumps(data)
print(text)Output:
{"name": "Alice", "age": 30}
text = '{"name": "Alice", "age": 30}'
data = json.loads(text)
print(data["name"])Use:
dump/loadfor files.dumps/loadsfor strings.
Example:
{
"user": {
"name": "Alice",
"contact": {
"email": "alice@example.com"
}
}
}Python:
email = data["user"]["contact"]["email"]
print(email)email = data.get("user", {}).get("contact", {}).get("email")This returns None instead of raising an error when a key is missing.
You can provide a default value:
name = data.get("name", "Unknown")Example:
{
"users": [
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 25}
]
}Loop through the array:
for user in data["users"]:
print(user["name"])Add an item:
data["users"].append({
"name": "Charlie",
"age": 28
})Remove an item:
data["users"].pop(0)Find a matching item:
user = next(
(user for user in data["users"] if user["name"] == "Bob"),
None
)Read, modify, and write the file:
import json
with open("user.json", encoding="utf-8") as file:
data = json.load(file)
data["age"] = 31
data["city"] = "London"
with open("user.json", "w", encoding="utf-8") as file:
json.dump(data, file, indent=4)JSON files do not update automatically when you modify the Python object. You must write the updated object back to the file.
from pathlib import Path
import json
path = Path("settings.json")
if not path.exists():
path.write_text(
json.dumps({"theme": "dark"}, indent=4),
encoding="utf-8"
)For most applications, a normal open() call is simpler:
try:
with open("settings.json", encoding="utf-8") as file:
settings = json.load(file)
except FileNotFoundError:
settings = {"theme": "light"}try:
with open("data.json", encoding="utf-8") as file:
data = json.load(file)
except FileNotFoundError:
print("The file was not found.")try:
with open("data.json", encoding="utf-8") as file:
data = json.load(file)
except json.JSONDecodeError:
print("The file contains invalid JSON.")try:
with open("data.json", encoding="utf-8") as file:
data = json.load(file)
except FileNotFoundError:
print("File not found.")
except json.JSONDecodeError:
print("Invalid JSON.")Other possible errors include:
PermissionError: insufficient file permissions.TypeError: trying to serialize an unsupported Python object.KeyError: accessing a missing dictionary key.IndexError: accessing an invalid list position.
import json
def is_valid_json(path):
try:
with open(path, encoding="utf-8") as file:
json.load(file)
return True
except (FileNotFoundError, json.JSONDecodeError):
return FalseUsage:
print(is_valid_json("data.json"))For a JSON string:
def is_valid_json_text(text):
try:
json.loads(text)
return True
except json.JSONDecodeError:
return FalseThe JSON syntax may be valid but the data may still be wrong.
required = ["name", "email"]
for field in required:
if field not in data:
raise ValueError(f"Missing field: {field}")Check value types:
if not isinstance(data.get("age"), int):
raise ValueError("Age must be an integer.")For larger applications, consider a validation library such as Pydantic or jsonschema.
Some Python objects cannot be directly converted to JSON:
from datetime import datetime
import json
data = {"created": datetime.now()}
json.dumps(data) # TypeErrorConvert the value first:
data["created"] = data["created"].isoformat()
text = json.dumps(data)Common conversions:
from datetime import date
data = {
"date": date.today().isoformat(),
"tags": list({"python", "json"})
}JSON can directly represent:
- Dictionaries
- Lists
- Strings
- Integers
- Floats
- Booleans
None
It cannot directly represent:
- Sets
- Dates
- Datetimes
- Custom classes
- File objects
- Database connections
Use UTF-8 when reading and writing:
with open("names.json", "w", encoding="utf-8") as file:
json.dump({"name": "José"}, file, ensure_ascii=False, indent=4)Without ensure_ascii=False, non-ASCII characters may be written as escaped Unicode sequences.
pathlib makes file paths easier to manage:
from pathlib import Path
import json
path = Path("data") / "users.json"
with path.open(encoding="utf-8") as file:
users = json.load(file)Write JSON:
path.write_text(
json.dumps(users, indent=4),
encoding="utf-8"
)Check for existence:
if path.exists():
print("File exists")import json
def read_json(path):
with open(path, encoding="utf-8") as file:
return json.load(file)
def write_json(path, data):
with open(path, "w", encoding="utf-8") as file:
json.dump(data, file, indent=4)Usage:
data = read_json("users.json")
data["count"] = len(data["users"])
write_json("users.json", data)A version with error handling:
def read_json(path, default=None):
try:
with open(path, encoding="utf-8") as file:
return json.load(file)
except (FileNotFoundError, json.JSONDecodeError):
return defaultIf a program stops while writing, the file could become incomplete. A safer approach is to write a temporary file first and then replace the original.
import json
from pathlib import Path
def safe_write_json(path, data):
path = Path(path)
temp_path = path.with_suffix(".tmp")
temp_path.write_text(
json.dumps(data, indent=4),
encoding="utf-8"
)
temp_path.replace(path)Usage:
safe_write_json("settings.json", {"theme": "dark"})This is useful for important configuration or data files.
A regular JSON file usually contains one complete value. A JSON Lines file stores one JSON object per line:
{"id": 1, "name": "Alice"}
{"id": 2, "name": "Bob"}Read it line by line:
import json
with open("users.jsonl", encoding="utf-8") as file:
for line in file:
user = json.loads(line)
print(user["name"])Write JSON Lines:
with open("users.jsonl", "w", encoding="utf-8") as file:
for user in users:
file.write(json.dumps(user) + "\n")JSON Lines is useful for:
- Large datasets
- Logs
- Streaming data
- Processing one record at a time
json.load() reads the entire file into memory:
data = json.load(file)For very large files:
- Prefer JSON Lines when possible.
- Process records incrementally.
- Use a streaming parser such as
ijsonfor large standard JSON documents. - Avoid repeatedly loading and rewriting a huge file.
A JSON array containing millions of objects is less convenient to stream than a JSON Lines file.
Python dictionaries preserve insertion order.
data = {
"zebra": 1,
"apple": 2
}To sort keys alphabetically when writing:
json.dump(data, file, indent=4, sort_keys=True)Compact output:
json.dump(data, file, separators=(",", ":"))JSON formatting may differ even when the data is the same.
import json
first = '{"name": "Alice", "age": 30}'
second = '{"age": 30, "name": "Alice"}'
same = json.loads(first) == json.loads(second)
print(same)Output:
True
Comparing parsed Python objects is better than comparing raw JSON strings.
A simple script can accept a file path:
import sys
import json
path = sys.argv[1]
with open(path, encoding="utf-8") as file:
data = json.load(file)
print(data)Run it:
python app.py users.jsonFor more advanced command-line tools, use argparse.
When working with JSON:
- Use
json.load()andjson.loads()for untrusted JSON. - Do not use
eval()to parse JSON. - Do not store passwords or secret keys in plain JSON files.
- Validate data before using it.
- Be cautious when JSON controls file paths, commands, database queries, or permissions.
- Limit the size of JSON received from external sources.
Avoid this:
data = eval(user_input)Use this instead:
data = json.loads(user_input)Invalid JSON:
{'name': 'Alice', 'active': True}Valid JSON:
{"name": "Alice", "active": true}data["age"] = 31This changes only the in-memory object. Save it:
with open("user.json", "w", encoding="utf-8") as file:
json.dump(data, file, indent=4)Risky:
email = data["email"]Safer:
email = data.get("email")Prefer:
open("data.json", encoding="utf-8")This is invalid as a normal JSON document:
{"id": 1}{"id": 2}
Use a JSON array:
[
{"id": 1},
{"id": 2}
]Or use JSON Lines:
{"id": 1}
{"id": 2}tasks.json:
{
"tasks": [
{"title": "Learn JSON", "done": false}
]
}Python program:
import json
path = "tasks.json"
with open(path, encoding="utf-8") as file:
data = json.load(file)
data["tasks"].append({
"title": "Practice Python",
"done": False
})
for task in data["tasks"]:
print(task["title"])
with open(path, "w", encoding="utf-8") as file:
json.dump(data, file, indent=4)This program:
- Opens a JSON file.
- Converts it to Python data.
- Adds a task.
- Reads values from the data.
- Saves the modified data.
When working with a JSON file:
- Decide what structure the JSON should have.
- Read the file with
json.load(). - Handle missing files and invalid JSON.
- Validate required fields and value types.
- Access data using dictionaries and lists.
- Modify the Python object.
- Write it back with
json.dump(). - Use
indent=4for human-readable files. - Use UTF-8 encoding.
- Use JSON Lines or a streaming parser for large datasets.
- Avoid
eval()and validate untrusted data. - Use temporary-file replacement when safe updates matter.
The core pattern is:
import json
with open("data.json", encoding="utf-8") as file:
data = json.load(file)
# Read or modify data here
with open("data.json", "w", encoding="utf-8") as file:
json.dump(data, file, indent=4)