Skip to main content

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

ScenarioMethodNotes
Flat dictionariesall(item in target.items() ...)Simple and readable
Hashable values onlyd1.items() <= d2.items()Fastest, but limited
Nested dictionariesRecursive functionHandles arbitrary depth
With listsExtended recursiveFull JSON support

Summary

  • Use all() with items() 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.