How to Validate Properties in Python Dictionaries
In Python, dictionaries are the go-to structure for handling dynamic data, configurations, and API responses. However, because dictionaries are mutable and schema-less by default, accessing missing keys or processing incorrect data types can lead to runtime errors like KeyError or TypeError.
This guide explores robust techniques to validate dictionary properties, ensuring data integrity through existence checks, type validation, and logical constraint verification.
Validating Key Existence
The most common error when working with dictionaries is the KeyError, which occurs when trying to access a key that does not exist.
Using in and .get()
user_data = {"name": "John", "role": "admin"}
# ⛔️ Incorrect: Accessing a missing key causes a crash
try:
email = user_data["email"]
except KeyError as e:
print(f"Error: Missing key {e}")
# ✅ Correct: Check existence before access
if "email" in user_data:
print(f"Email: {user_data['email']}")
else:
print("Email not found.")
# ✅ Correct: Using .get() for a safe fallback
# Returns None (or a specified default) if the key is missing
email_safe = user_data.get("email", "No Email Provided")
print(f"Result: {email_safe}")
Output:
Error: Missing key 'email'
Email not found.
Result: No Email Provided
Prefer the .get() method when you simply need a default value. Use the if key in dict pattern when the logic requires distinct branches of execution based on the key's presence.
Validating Value Types
Ensuring that a value corresponds to a specific data type (e.g., age must be an int) prevents downstream calculation errors.
Using isinstance()
data = {
"age": "twenty-five", # Incorrect type
"active": True
}
# ⛔️ Incorrect: Performing math on a string raises TypeError
try:
next_age = data["age"] + 1
except TypeError as e:
print(f"Math Error: {e}")
# ✅ Correct: Validate type before processing
def validate_age_type(record):
if not isinstance(record.get("age"), int):
print("Validation Failed: 'age' must be an integer.")
return False
return True
validate_age_type(data)
Output:
Math Error: can only concatenate str (not "int") to str
Validation Failed: 'age' must be an integer.
Batch Type Validation
You can validate an entire dictionary against a schema of expected types.
def validate_schema(data, schema):
return all(isinstance(data.get(k), t) for k, t in schema.items())
user_input = {"name": "Alice", "id": 101}
expected_schema = {"name": str, "id": int}
# ✅ Correct: Validates all fields match the schema
is_valid = validate_schema(user_input, expected_schema)
print(f"Schema Valid: {is_valid}")
Output:
Schema Valid: True
Validating Value Constraints (Ranges)
Sometimes data is the correct type (e.g., an integer) but logically invalid (e.g., a negative age). This requires value checking.
def validate_constraints(data):
age = data.get("age")
# 1. Type Check
if not isinstance(age, int):
return False, "Age must be an integer"
# 2. Value/Constraint Check
if not (0 <= age <= 120):
return False, f"Age {age} is out of valid range (0-120)"
return True, "Valid"
# ✅ Correct: Validates logic constraints
print(validate_constraints({"age": 150}))
print(validate_constraints({"age": 25}))
Output:
(False, 'Age 150 is out of valid range (0-120)')
(True, 'Valid')
Comprehensive Validation Pattern
For production code, it is best to combine existence, type, and constraint checks into a robust validator function or class.
class DictionaryValidationError(Exception):
pass
def validate_user_profile(profile):
# Schema definition: key -> (type, validator_function)
schema = {
"username": (str, lambda x: len(x) >= 3),
"age": (int, lambda x: x > 0),
"email": (str, lambda x: "@" in x)
}
for key, (expected_type, validator) in schema.items():
# 1. Existence Check
if key not in profile:
raise DictionaryValidationError(f"Missing required key: '{key}'")
value = profile[key]
# 2. Type Check
if not isinstance(value, expected_type):
raise DictionaryValidationError(
f"Incorrect type for '{key}'. Expected {expected_type.__name__}, got {type(value).__name__}"
)
# 3. Constraint Check
if not validator(value):
raise DictionaryValidationError(f"Invalid value for '{key}': {value}")
print("Profile is valid.")
# Test Case
try:
user = {"username": "Jo", "age": 30, "email": "jo@test.com"}
validate_user_profile(user)
except DictionaryValidationError as e:
print(f"Validation Error: {e}")
Output:
Validation Error: Invalid value for 'username': Jo
In the example above, the validation failed because the username "Jo" is shorter than the required 3 characters defined in the schema lambda.
Conclusion
Proper dictionary validation involves three steps:
- Existence: Check if keys exist using
inor.get(). - Type: Ensure values are of the expected class using
isinstance(). - Constraint: Verify values meet logical requirements (ranges, length, format).
By implementing these checks, you prevent data corruption and ensure your application handles external input gracefully.