How to Read and Write JSON in Python: Complete Guide
Python's built-in json module makes working with JSON data straightforward. Whether you're reading configuration files, consuming APIs, or storing data, this guide covers everything you need to know about handling JSON in Python.
Quick Start
Here's the essentials in 30 seconds:
import json
# Parse JSON string → Python dict
data = json.loads('{"name": "Alice", "age": 30}')
# Python dict → JSON string
json_string = json.dumps({"name": "Alice", "age": 30})
# Read JSON file
with open('data.json', 'r') as f:
data = json.load(f)
# Write JSON file
with open('data.json', 'w') as f:
json.dump(data, f, indent=2)
Reading JSON
From a String: json.loads()
Use loads() (load string) to parse a JSON string into a Python object:
import json
json_string = '{"name": "Alice", "age": 30, "active": true}'
data = json.loads(json_string)
print(data['name']) # Alice
print(data['age']) # 30
print(data['active']) # True (converted to Python bool)
print(type(data)) # <class 'dict'>
From a File: json.load()
Use load() (no 's') to read directly from a file:
import json
with open('config.json', 'r', encoding='utf-8') as file:
config = json.load(file)
print(config)
Always specify encoding='utf-8' to handle special characters correctly.
From an API Response
When working with the requests library:
import requests
response = requests.get('https://api.example.com/data')
# Method 1: Use response.json() (recommended)
data = response.json()
# Method 2: Parse manually
data = json.loads(response.text)
Writing JSON
To a String: json.dumps()
Use dumps() (dump string) to convert Python objects to JSON:
import json
data = {
"name": "Alice",
"age": 30,
"languages": ["Python", "JavaScript"],
"active": True
}
# Basic conversion
json_string = json.dumps(data)
print(json_string)
# {"name": "Alice", "age": 30, "languages": ["Python", "JavaScript"], "active": true}
To a File: json.dump()
Use dump() to write directly to a file:
import json
data = {"name": "Alice", "scores": [95, 87, 92]}
with open('output.json', 'w', encoding='utf-8') as file:
json.dump(data, file)
Formatting Options
Pretty Printing
Use indent for readable output:
import json
data = {"name": "Alice", "address": {"city": "Boston", "zip": "02101"}}
# Compact (default)
print(json.dumps(data))
# {"name": "Alice", "address": {"city": "Boston", "zip": "02101"}}
# Pretty printed
print(json.dumps(data, indent=2))
# {
# "name": "Alice",
# "address": {
# "city": "Boston",
# "zip": "02101"
# }
# }
Sorting Keys
Use sort_keys for consistent output:
data = {"zebra": 1, "apple": 2, "mango": 3}
print(json.dumps(data, sort_keys=True, indent=2))
# {
# "apple": 2,
# "mango": 3,
# "zebra": 1
# }
Compact Output
Remove whitespace for smaller file sizes:
# Remove spaces after separators
compact = json.dumps(data, separators=(',', ':'))
Type Conversions
Python and JSON types map as follows:
| Python | JSON |
|---|---|
dict |
object |
list, tuple |
array |
str |
string |
int, float |
number |
True |
true |
False |
false |
None |
null |
Handling Unsupported Types
JSON doesn't support all Python types. These will raise TypeError:
import json
from datetime import datetime
data = {
"timestamp": datetime.now(), # Not JSON serializable!
"data": {1, 2, 3} # Sets not supported!
}
json.dumps(data) # TypeError
Custom Encoder for Dates
Handle datetime objects with a custom encoder:
import json
from datetime import datetime, date
class DateTimeEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, (datetime, date)):
return obj.isoformat()
return super().default(obj)
data = {"created": datetime.now(), "name": "Report"}
json_string = json.dumps(data, cls=DateTimeEncoder)
print(json_string)
# {"created": "2024-01-15T10:30:00.123456", "name": "Report"}
Using the default Parameter
For simple cases, use the default parameter:
import json
from datetime import datetime
def json_serializer(obj):
if isinstance(obj, datetime):
return obj.isoformat()
if isinstance(obj, set):
return list(obj)
raise TypeError(f"Type {type(obj)} not serializable")
data = {
"timestamp": datetime.now(),
"tags": {"python", "json", "tutorial"}
}
print(json.dumps(data, default=json_serializer, indent=2))
Error Handling
Catching Parse Errors
Always wrap JSON parsing in try-except:
import json
def safe_parse(json_string):
try:
return json.loads(json_string)
except json.JSONDecodeError as e:
print(f"Invalid JSON at line {e.lineno}, column {e.colno}")
print(f"Error: {e.msg}")
return None
# Test with invalid JSON
result = safe_parse('{"name": "Alice",}') # Trailing comma
# Invalid JSON at line 1, column 18
# Error: Expecting property name enclosed in double quotes
Common Errors and Fixes
JSONDecodeError: Expecting property name
- Cause: Trailing comma or single quotes
- Fix: Remove trailing commas, use double quotes
JSONDecodeError: Expecting value
- Cause: Empty string or malformed JSON
- Fix: Check input isn't empty, validate JSON structure
JSONDecodeError: Invalid control character
- Cause: Unescaped newlines or tabs in strings
- Fix: Escape special characters or use raw strings
See our JSON Parse Error guide for detailed solutions.
Working with Nested JSON
Accessing Nested Data
import json
data = {
"user": {
"name": "Alice",
"contacts": {
"email": "alice@example.com",
"phone": ["555-1234", "555-5678"]
}
}
}
# Access nested values
email = data["user"]["contacts"]["email"]
first_phone = data["user"]["contacts"]["phone"][0]
# Safe access with .get()
website = data["user"]["contacts"].get("website", "Not provided")
Flattening Nested JSON
For deeply nested structures, consider flattening:
def flatten_json(data, prefix=''):
result = {}
for key, value in data.items():
new_key = f"{prefix}.{key}" if prefix else key
if isinstance(value, dict):
result.update(flatten_json(value, new_key))
else:
result[new_key] = value
return result
nested = {"user": {"name": "Alice", "address": {"city": "Boston"}}}
flat = flatten_json(nested)
# {"user.name": "Alice", "user.address.city": "Boston"}
Or use our JSON Flatten tool for quick results.
Working with JSON Lines (JSONL)
JSON Lines format stores one JSON object per line:
import json
# Reading JSONL
def read_jsonl(filename):
with open(filename, 'r') as f:
return [json.loads(line) for line in f if line.strip()]
# Writing JSONL
def write_jsonl(filename, data_list):
with open(filename, 'w') as f:
for item in data_list:
f.write(json.dumps(item) + '\n')
# Example
users = [
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 25}
]
write_jsonl('users.jsonl', users)
Performance Tips
Large Files: Use Streaming
For very large JSON files, process line by line:
import json
def process_large_json_array(filename):
"""Process large JSON array without loading entire file."""
with open(filename, 'r') as f:
# Skip opening bracket
f.read(1)
buffer = ''
for line in f:
buffer += line
if line.strip().endswith(',') or line.strip() == ']':
try:
# Try to parse accumulated buffer
obj = json.loads(buffer.rstrip(',\n]'))
yield obj
buffer = ''
except json.JSONDecodeError:
continue
Use orjson for Speed
For performance-critical applications, consider orjson:
# pip install orjson
import orjson
# 3-10x faster than standard json
data = orjson.loads(json_bytes)
json_bytes = orjson.dumps(data)
Use ujson as Alternative
# pip install ujson
import ujson
# Faster than standard library
data = ujson.loads(json_string)
json_string = ujson.dumps(data)
Real-World Examples
Reading a Config File
import json
from pathlib import Path
def load_config(config_path='config.json'):
"""Load configuration with defaults."""
defaults = {
"debug": False,
"port": 8080,
"host": "localhost"
}
config_file = Path(config_path)
if config_file.exists():
with open(config_file, 'r') as f:
user_config = json.load(f)
return {**defaults, **user_config}
return defaults
config = load_config()
print(f"Server running on {config['host']}:{config['port']}")
Consuming a REST API
import json
import requests
def fetch_user(user_id):
"""Fetch user data from API with error handling."""
try:
response = requests.get(
f'https://api.example.com/users/{user_id}',
timeout=10
)
response.raise_for_status()
return response.json()
except requests.RequestException as e:
print(f"Request failed: {e}")
return None
except json.JSONDecodeError:
print("Invalid JSON response")
return None
user = fetch_user(123)
if user:
print(f"Hello, {user['name']}!")
Saving Application State
import json
from pathlib import Path
class AppState:
def __init__(self, state_file='state.json'):
self.state_file = Path(state_file)
self.data = self._load()
def _load(self):
if self.state_file.exists():
with open(self.state_file, 'r') as f:
return json.load(f)
return {}
def save(self):
with open(self.state_file, 'w') as f:
json.dump(self.data, f, indent=2)
def get(self, key, default=None):
return self.data.get(key, default)
def set(self, key, value):
self.data[key] = value
self.save()
# Usage
state = AppState()
state.set('last_run', '2024-01-15')
state.set('processed_count', 42)
Related Tools & Resources
Tools
- JSON Validator — Validate your JSON before parsing
- JSON Flatten — Flatten nested structures
- JSON to CSV — Convert JSON to spreadsheet format
- JSON to YAML — Convert to YAML for config files
Learn More
- JSON Parse Error Guide — Fix common parsing errors
- Common JSON Mistakes — Avoid syntax errors
- JSON Comments Guide — Why comments cause errors
- Mastering JSON Format — JSON fundamentals
Related Articles
Understanding the Benefits of Using a JSON Beautifier
Learn why formatting and beautifying JSON improves code readability, debugging, and collaboration.
Common JSON Mistakes and How to Avoid Them
The 20 most common JSON syntax errors developers make, organized by category with examples and fixes.
How to Fix 'Unexpected End of JSON Input' Error
Learn what causes the 'Unexpected end of JSON input' error and how to fix it with practical solutions for JavaScript, Python, and other languages.
How to Open JSON Files
A complete guide to opening and viewing JSON files on any operating system using various methods and tools.