I'll be honest—when I first started working with JSON in Python, I was confused. I kept mixing up json.load() and json.loads(). I'd try to read a file and get errors. I'd try to write data and end up with a mess.
It took me way too long to figure out the difference between working with files and working with strings. Once I got it, everything clicked. Now I work with JSON files in Python almost daily, and it's become second nature.
Let me save you the confusion I went through. Here's everything you need to know about reading and writing JSON files in Python, explained clearly with real examples.
The Python json Module
Python has a built-in json module. You don't need to install anything—it's part of the standard library. Just import it:
import json
That's it. You're ready to work with JSON.
Reading JSON Files
There are two ways to read JSON in Python, and the difference matters:
json.load() - Reading from a File
Use json.load() when you want to read JSON from a file:
import json
# Open and read a JSON file
with open('data.json', 'r') as f:
data = json.load(f)
print(data['name']) # Access the data
The with statement automatically closes the file when you're done. Always use it—it's a best practice.
json.loads() - Reading from a String
Use json.loads() (note the 's' for 'string') when you have JSON as a string:
import json
json_string = '{"name": "John", "age": 30}'
data = json.loads(json_string)
print(data['name']) # "John"
Remember: load() is for files, loads() is for strings. The 's' stands for 'string'.
Writing JSON Files
Similarly, there are two ways to write JSON:
json.dump() - Writing to a File
Use json.dump() to write JSON directly to a file:
import json
data = {
"name": "John",
"age": 30,
"city": "New York"
}
# Write to a file
with open('output.json', 'w') as f:
json.dump(data, f)
This creates a file called output.json with your data.
json.dumps() - Writing to a String
Use json.dumps() (again, 's' for 'string') to convert data to a JSON string:
import json
data = {"name": "John", "age": 30}
json_string = json.dumps(data)
print(json_string) # '{"name": "John", "age": 30}'
Remember: dump() is for files, dumps() is for strings.
Pretty Printing JSON
By default, json.dump() and json.dumps() create compact JSON (all on one line). For readable output, use the indent parameter:
import json
data = {
"name": "John",
"age": 30,
"address": {
"street": "123 Main St",
"city": "New York"
}
}
# Pretty print to file
with open('pretty.json', 'w') as f:
json.dump(data, f, indent=2)
# Pretty print to string
pretty_string = json.dumps(data, indent=2)
print(pretty_string)
The indent=2 parameter adds 2-space indentation, making the JSON readable.
Common Patterns
Let me show you some real-world patterns I use all the time:
Pattern 1: Reading Configuration Files
import json
def load_config(config_path='config.json'):
try:
with open(config_path, 'r') as f:
return json.load(f)
except FileNotFoundError:
print(f"Config file {config_path} not found")
return {}
except json.JSONDecodeError as e:
print(f"Invalid JSON in config file: {e}")
return {}
# Usage
config = load_config()
database_url = config.get('database_url', 'localhost')
Pattern 2: Saving User Data
import json
def save_user_data(user_id, user_data):
filename = f'users/{user_id}.json'
with open(filename, 'w') as f:
json.dump(user_data, f, indent=2)
print(f"Saved user data to {filename}")
# Usage
user_data = {
"name": "John",
"email": "john@example.com",
"preferences": {
"theme": "dark",
"notifications": True
}
}
save_user_data(123, user_data)
Pattern 3: Reading API Responses
When working with APIs, you often get JSON strings that need parsing:
import json
import requests
response = requests.get('https://api.example.com/users/123')
data = json.loads(response.text) # Parse the response string
# Or, if the response has json() method:
data = response.json() # This does json.loads() for you
Pattern 4: Updating Existing JSON Files
import json
def update_json_file(filename, updates):
# Read existing data
try:
with open(filename, 'r') as f:
data = json.load(f)
except FileNotFoundError:
data = {}
# Update with new data
data.update(updates)
# Write back
with open(filename, 'w') as f:
json.dump(data, f, indent=2)
# Usage
update_json_file('config.json', {
'last_updated': '2025-01-17',
'version': '2.0'
})
Error Handling
Always handle errors when working with JSON files. Here's a robust pattern:
import json
def safe_read_json(filename):
try:
with open(filename, 'r') as f:
return json.load(f)
except FileNotFoundError:
print(f"File {filename} not found")
return None
except json.JSONDecodeError as e:
print(f"Invalid JSON in {filename}: {e}")
print(f"Error at line {e.lineno}, column {e.colno}")
return None
except Exception as e:
print(f"Unexpected error: {e}")
return None
# Usage
data = safe_read_json('data.json')
if data:
print("Successfully loaded JSON")
else:
print("Failed to load JSON")
Working with Nested Data
JSON often has nested structures. Here's how to work with them:
import json
# Reading nested data
with open('data.json', 'r') as f:
data = json.load(f)
# Access nested values
user_name = data['user']['profile']['name']
user_email = data['user']['profile']['email']
# Or use .get() to avoid KeyError
user_name = data.get('user', {}).get('profile', {}).get('name', 'Unknown')
# Modifying nested data
data['user']['profile']['last_login'] = '2025-01-17'
# Save the updated data
with open('data.json', 'w') as f:
json.dump(data, f, indent=2)
Advanced Options
The json module has several useful options:



