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.

Liked this? There's more.

Every week: one practical technique, explained simply, with code you can use immediately.