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.