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"}
try:
email = user_data["email"]
except KeyError as e:
print(f"Error: Missing key {e}")
if "email" in user_data:
print(f"Email: {user_data['email']}")
else:
print("Email not found.")
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 ProvidedValidating 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
}
try:
next_age = data["age"] + 1
except TypeError as e:
print(f"Math Error: {e}")
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}
is_valid = validate_schema(user_input, expected_schema)
print(f"Schema Valid: {is_valid}")Output:
Schema Valid: TrueValidating 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")
if not isinstance(age, int):
return False, "Age must be an integer"
if not (0 <= age <= 120):
return False, f"Age {age} is out of valid range (0-120)"
return True, "Valid"
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 = {
"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():
if key not in profile:
raise DictionaryValidationError(f"Missing required key: '{key}'")
value = profile[key]
if not isinstance(value, expected_type):
raise DictionaryValidationError(
f"Incorrect type for '{key}'. Expected {expected_type.__name__}, got {type(value).__name__}"
)
if not validator(value):
raise DictionaryValidationError(f"Invalid value for '{key}': {value}")
print("Profile is valid.")
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': JoConclusion
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.