How to Check if a Dictionary is a Subset of Another Dictionary in Python
In API testing and configuration validation, we often need to verify that a dictionary contains all keys and matching values from another dictionary, while ignoring extra keys. This "subset" check is essential for partial matching scenarios.
Using all() for Flat Dictionaries
The most Pythonic approach for simple dictionaries uses all() with a generator expression:
target = {"color": "red", "size": "L", "price": 100, "stock": 50}
query = {"color": "red", "size": "L"}
# Check if every (key, value) pair in query exists in target
is_subset = all(item in target.items() for item in query.items())
print(is_subset) # True
As a Reusable Function
def is_subset(subset: dict, superset: dict) -> bool:
"""Check if all key-value pairs in subset exist in superset."""
return all(item in superset.items() for item in subset.items())
# Test cases
config = {"debug": True, "port": 8080, "host": "localhost"}
print(is_subset({"debug": True}, config)) # True
print(is_subset({"port": 8080, "host": "localhost"}, config)) # True
print(is_subset({"port": 3000}, config)) # False (wrong value)
print(is_subset({"missing": True}, config)) # False (missing key)
Using Set Comparison
When dictionary values are hashable (strings, numbers, tuples), you can use set operators:
d1 = {"a": 1, "b": 2}
d2 = {"a": 1, "b": 2, "c": 3}
# d1 is subset of d2
print(d1.items() <= d2.items()) # True
# d2 is superset of d1
print(d2.items() >= d1.items()) # True
Hashable Values Only
This method fails if your dictionary contains unhashable types like lists:
d1 = {"tags": ["python", "code"]}
d2 = {"tags": ["python", "code"], "id": 1}
d1.items() <= d2.items() # TypeError: unhashable type: 'list'
Recursive Check for Nested Dictionaries
For nested structures like JSON responses, the flat approach fails because it doesn't look into nested dictionaries. Use recursion:
def is_subset_deep(subset: dict, superset: dict) -> bool:
"""Recursively check if subset is contained within superset."""
for key, value in subset.items():
# Key must exist in superset
if key not in superset:
return False
superset_value = superset[key]
# Both are dicts: recurse
if isinstance(value, dict) and isinstance(superset_value, dict):
if not is_subset_deep(value, superset_value):
return False
# Otherwise: direct comparison
elif superset_value != value:
return False
return True
# Nested example
api_response = {
"user": {
"id": 1,
"profile": {
"name": "Alice",
"role": "admin",
"settings": {"theme": "dark", "notifications": True}
}
},
"timestamp": "2024-01-15"
}
# Check for specific nested values
query = {
"user": {
"profile": {
"role": "admin",
"settings": {"theme": "dark"}
}
}
}
print(is_subset_deep(query, api_response)) # True
Handling Lists in Nested Structures
Extend the recursive function to handle lists:
def is_subset_deep_extended(subset, superset) -> bool:
"""Deep subset check supporting dicts and lists."""
if isinstance(subset, dict) and isinstance(superset, dict):
for key, value in subset.items():
if key not in superset:
return False
if not is_subset_deep_extended(value, superset[key]):
return False
return True
elif isinstance(subset, list) and isinstance(superset, list):
# Check if all items in subset list exist in superset list
for item in subset:
found = any(
is_subset_deep_extended(item, sup_item)
if isinstance(item, (dict, list)) else item == sup_item
for sup_item in superset
)
if not found:
return False
return True
else:
return subset == superset
# Example with lists
data = {
"users": [
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"}
],
"count": 2
}
query = {
"users": [{"name": "Alice"}]
}
print(is_subset_deep_extended(query, data)) # True
Finding Missing or Mismatched Keys
Get details about what's missing or different:
def compare_dicts(subset: dict, superset: dict) -> dict:
"""Compare dicts and return details about differences."""
missing_keys = []
mismatched_values = []
for key, value in subset.items():
if key not in superset:
missing_keys.append(key)
elif superset[key] != value:
mismatched_values.append({
"key": key,
"expected": value,
"actual": superset[key]
})
return {
"is_subset": len(missing_keys) == 0 and len(mismatched_values) == 0,
"missing_keys": missing_keys,
"mismatched_values": mismatched_values
}
expected = {"status": "success", "code": 200, "data": "test"}
actual = {"status": "error", "code": 500}
result = compare_dicts(expected, actual)
print(result)
Output:
{
'is_subset': False,
'missing_keys': ['data'],
'mismatched_values': [
{'key': 'status', 'expected': 'success', 'actual': 'error'},
{'key': 'code', 'expected': 200, 'actual': 500}
]
}
Practical Example: API Response Validation
def validate_api_response(response: dict, required_fields: dict) -> tuple[bool, list]:
"""Validate that API response contains required fields and values."""
errors = []
def check(subset, superset, path=""):
for key, expected in subset.items():
current_path = f"{path}.{key}" if path else key
if key not in superset:
errors.append(f"Missing field: {current_path}")
continue
actual = superset[key]
if isinstance(expected, dict) and isinstance(actual, dict):
check(expected, actual, current_path)
elif isinstance(expected, type):
if not isinstance(actual, expected):
errors.append(
f"Type mismatch at {current_path}: "
f"expected {expected.__name__}, got {type(actual).__name__}"
)
elif actual != expected:
errors.append(
f"Value mismatch at {current_path}: "
f"expected {expected!r}, got {actual!r}"
)
check(required_fields, response)
return len(errors) == 0, errors
# Usage
response = {"status": "ok", "data": {"id": 123, "name": "Test"}}
required = {"status": "ok", "data": {"id": int}} # int means "any integer"
valid, errors = validate_api_response(response, required)
print(f"Valid: {valid}, Errors: {errors}") # Output: Valid: True, Errors: []
Method Comparison
| Scenario | Method | Notes |
|---|---|---|
| Flat dictionaries | all(item in target.items() ...) | Simple and readable |
| Hashable values only | d1.items() <= d2.items() | Fastest, but limited |
| Nested dictionaries | Recursive function | Handles arbitrary depth |
| With lists | Extended recursive | Full JSON support |
Summary
- Use
all()withitems()for simple flat dictionary subset checks. - Use set operators (
<=) when values are guaranteed hashable. - Use recursive functions for nested JSON-like structures.
- Consider returning details about mismatches for debugging and testing.