Python - Nested Dictionary with Examples
A nested dictionary is a dictionary where values can be other dictionaries, creating a tree-like data structure. This pattern appears frequently when working with JSON APIs, configuration files, or...
Key Insights
- Nested dictionaries in Python allow you to structure hierarchical data efficiently, enabling multi-level data organization through dictionary values that are themselves dictionaries
- Access patterns for nested data include bracket notation, the
get()method with defaults, and dictionary comprehensions for transformations and filtering - Production code requires defensive programming with existence checks, the
setdefault()method for safe insertions, and recursive functions for deep operations on nested structures
Understanding Nested Dictionary Structure
A nested dictionary is a dictionary where values can be other dictionaries, creating a tree-like data structure. This pattern appears frequently when working with JSON APIs, configuration files, or any hierarchical data.
user_data = {
"user_001": {
"name": "Alice Johnson",
"email": "alice@example.com",
"preferences": {
"theme": "dark",
"notifications": True,
"language": "en"
}
},
"user_002": {
"name": "Bob Smith",
"email": "bob@example.com",
"preferences": {
"theme": "light",
"notifications": False,
"language": "es"
}
}
}
# Access nested values
print(user_data["user_001"]["name"]) # Alice Johnson
print(user_data["user_001"]["preferences"]["theme"]) # dark
Safe Access Patterns
Direct bracket notation raises KeyError when keys don’t exist. Use get() for defensive access with fallback values.
config = {
"database": {
"host": "localhost",
"port": 5432
},
"cache": {
"enabled": True
}
}
# Unsafe - raises KeyError
# timeout = config["database"]["timeout"]
# Safe with get()
timeout = config.get("database", {}).get("timeout", 30)
print(timeout) # 30 (default value)
# Multiple level safe access
cache_ttl = config.get("cache", {}).get("ttl", 3600)
print(cache_ttl) # 3600
# Check existence before access
if "database" in config and "timeout" in config["database"]:
timeout = config["database"]["timeout"]
else:
timeout = 30
Building Nested Dictionaries Dynamically
The setdefault() method creates intermediate dictionaries automatically, preventing KeyError when building nested structures.
# Building a nested structure from flat data
transactions = [
("2024-01", "electronics", 299.99),
("2024-01", "books", 45.50),
("2024-02", "electronics", 599.99),
("2024-02", "books", 23.99),
("2024-02", "electronics", 149.99)
]
sales_by_month = {}
for month, category, amount in transactions:
sales_by_month.setdefault(month, {}).setdefault(category, []).append(amount)
print(sales_by_month)
# {
# '2024-01': {'electronics': [299.99], 'books': [45.5]},
# '2024-02': {'electronics': [599.99, 149.99], 'books': [23.99]}
# }
# Calculate totals
monthly_totals = {}
for month, categories in sales_by_month.items():
monthly_totals[month] = {
category: sum(amounts)
for category, amounts in categories.items()
}
print(monthly_totals)
# {'2024-01': {'electronics': 299.99, 'books': 45.5},
# '2024-02': {'electronics': 749.98, 'books': 23.99}}
Dictionary Comprehensions with Nested Data
Transform and filter nested dictionaries using comprehensions for concise, readable code.
products = {
"prod_001": {
"name": "Laptop",
"price": 999.99,
"stock": 15,
"category": "electronics"
},
"prod_002": {
"name": "Mouse",
"price": 29.99,
"stock": 0,
"category": "electronics"
},
"prod_003": {
"name": "Desk",
"price": 299.99,
"stock": 5,
"category": "furniture"
}
}
# Filter products in stock
in_stock = {
pid: details
for pid, details in products.items()
if details["stock"] > 0
}
# Extract specific fields
product_prices = {
details["name"]: details["price"]
for details in products.values()
}
# Nested comprehension - group by category
by_category = {}
for pid, details in products.items():
category = details["category"]
if category not in by_category:
by_category[category] = {}
by_category[category][pid] = details
# Or using setdefault
by_category_alt = {}
for pid, details in products.items():
by_category_alt.setdefault(details["category"], {})[pid] = details
Recursive Operations on Nested Dictionaries
Handle arbitrary nesting depths with recursive functions for operations like flattening, deep merging, or searching.
def flatten_dict(nested_dict, parent_key='', sep='_'):
"""Flatten a nested dictionary into a single level."""
items = []
for key, value in nested_dict.items():
new_key = f"{parent_key}{sep}{key}" if parent_key else key
if isinstance(value, dict):
items.extend(flatten_dict(value, new_key, sep=sep).items())
else:
items.append((new_key, value))
return dict(items)
nested = {
"user": {
"profile": {
"name": "Alice",
"age": 30
},
"settings": {
"theme": "dark"
}
}
}
flat = flatten_dict(nested)
print(flat)
# {'user_profile_name': 'Alice', 'user_profile_age': 30, 'user_settings_theme': 'dark'}
def deep_merge(dict1, dict2):
"""Deep merge two dictionaries."""
result = dict1.copy()
for key, value in dict2.items():
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
result[key] = deep_merge(result[key], value)
else:
result[key] = value
return result
base_config = {
"database": {"host": "localhost", "port": 5432},
"logging": {"level": "INFO"}
}
override_config = {
"database": {"port": 5433, "ssl": True},
"cache": {"enabled": True}
}
merged = deep_merge(base_config, override_config)
print(merged)
# {
# 'database': {'host': 'localhost', 'port': 5433, 'ssl': True},
# 'logging': {'level': 'INFO'},
# 'cache': {'enabled': True}
# }
Searching and Filtering Nested Structures
Implement search functions to find values or paths within deeply nested dictionaries.
def find_key_path(nested_dict, target_key, current_path=None):
"""Find all paths to a specific key in nested dictionary."""
if current_path is None:
current_path = []
paths = []
for key, value in nested_dict.items():
new_path = current_path + [key]
if key == target_key:
paths.append(new_path)
if isinstance(value, dict):
paths.extend(find_key_path(value, target_key, new_path))
return paths
data = {
"level1": {
"level2a": {
"target": "value1"
},
"level2b": {
"level3": {
"target": "value2"
}
}
},
"target": "value3"
}
paths = find_key_path(data, "target")
for path in paths:
print(" -> ".join(path))
# target
# level1 -> level2a -> target
# level1 -> level2b -> level3 -> target
def get_by_path(nested_dict, path):
"""Access value using dot notation path."""
keys = path.split('.')
value = nested_dict
for key in keys:
if isinstance(value, dict):
value = value.get(key)
if value is None:
return None
else:
return None
return value
config = {
"app": {
"database": {
"connection": {
"timeout": 30
}
}
}
}
timeout = get_by_path(config, "app.database.connection.timeout")
print(timeout) # 30
Updating Nested Values
Modify nested dictionaries safely, handling missing intermediate keys.
def set_nested_value(dictionary, keys, value):
"""Set a value in nested dictionary, creating intermediate dicts."""
for key in keys[:-1]:
dictionary = dictionary.setdefault(key, {})
dictionary[keys[-1]] = value
config = {}
set_nested_value(config, ["server", "database", "host"], "localhost")
set_nested_value(config, ["server", "database", "port"], 5432)
set_nested_value(config, ["server", "cache", "enabled"], True)
print(config)
# {
# 'server': {
# 'database': {'host': 'localhost', 'port': 5432},
# 'cache': {'enabled': True}
# }
# }
def update_nested(dictionary, path, value):
"""Update using dot notation path."""
keys = path.split('.')
set_nested_value(dictionary, keys, value)
settings = {"app": {"theme": "light"}}
update_nested(settings, "app.notifications.email", True)
print(settings)
# {'app': {'theme': 'light', 'notifications': {'email': True}}}
Converting Between Formats
Transform nested dictionaries to and from other formats like JSON or flat key-value pairs.
import json
def to_json_string(nested_dict, indent=2):
"""Convert nested dict to formatted JSON string."""
return json.dumps(nested_dict, indent=indent)
def from_flat_keys(flat_dict, sep='_'):
"""Convert flat dictionary with separator keys to nested."""
nested = {}
for flat_key, value in flat_dict.items():
keys = flat_key.split(sep)
set_nested_value(nested, keys, value)
return nested
flat_data = {
"user_name": "Alice",
"user_email": "alice@example.com",
"user_preferences_theme": "dark"
}
nested_data = from_flat_keys(flat_data)
print(json.dumps(nested_data, indent=2))
# {
# "user": {
# "name": "Alice",
# "email": "alice@example.com",
# "preferences": {
# "theme": "dark"
# }
# }
# }
Nested dictionaries provide flexible data organization for complex applications. Use safe access patterns in production, leverage recursion for deep operations, and implement helper functions for common tasks like flattening and path-based access.