Type Checking in Data Validation
In this tutorial, you'll learn about Type Checking. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
Type checking validates that input values match expected data types before being processed, preventing type-related errors that crash applications.
What You'll Learn
By the end of this lesson, you will implement type validators for primitives, complex types, nullable types, and union types.
Why It Matters
Languages like Python are dynamically typed. A value that arrives as a string where an integer is expected causes a TypeError. Type checking catches this early.
Real-World Use
DodaZIP validates that file metadata fields match expected types: file_size must be int, file_name must be str, created_at must be a datetime.
Type Validator
# type_checker.py
from typing import Any, Dict, List, Optional, Type, Union
class TypeChecker:
@staticmethod
def check(value: Any, expected_type: Type) -> Optional[str]:
if value is None:
return None
if expected_type == int:
if isinstance(value, bool):
return "bool is not int"
if isinstance(value, (int, float)):
if isinstance(value, float) and not value.is_integer():
return "expected integer, got float"
return None
return "expected integer"
if expected_type == float:
if isinstance(value, (int, float)):
return None
return "expected float"
if expected_type == str:
if isinstance(value, str):
return None
return "expected string"
if expected_type == bool:
if isinstance(value, bool):
return None
return "expected boolean"
if expected_type == list:
if isinstance(value, list):
return None
return "expected array"
if expected_type == dict:
if isinstance(value, dict):
return None
return "expected object"
if not isinstance(value, expected_type):
return f"expected {expected_type.__name__}"
return None
@staticmethod
def check_all(data: Dict, schema: Dict[str, Type]) -> List[Dict]:
errors = []
for field, expected_type in schema.items():
value = data.get(field)
error = TypeChecker.check(value, expected_type)
if error and value is not None:
errors.append({"field": field, "code": "type_error", "expected": expected_type.__name__})
if error and value is None and expected_type != type(None):
pass
return errors
tc = TypeChecker()
print(tc.check("hello", int)) # string -> int
print(tc.check(42, int)) # int -> int
print(tc.check(True, int)) # bool -> int (returns error in strict mode)
print(tc.check([1, 2], list)) # list -> list
print(tc.check({"a": 1}, dict)) # dict -> dict
Expected output:
expected integer
None
bool is not int
None
None
Nullable and Union Types
# nullable_types.py
from typing import Any, Dict, List, Optional, Tuple, Type
class AdvancedTypeChecker:
def __init__(self, strict_bool: bool = True):
self.strict_bool = strict_bool
def check(self, value: Any, type_spec: Any) -> Optional[str]:
if isinstance(type_spec, tuple):
return self._check_union(value, type_spec)
if type_spec == "nullable_int":
return None if value is None else self._check_primitive(value, int)
if type_spec == "nullable_str":
return None if value is None else self._check_primitive(value, str)
return self._check_primitive(value, type_spec)
def _check_union(self, value: Any, types: Tuple) -> Optional[str]:
if value is None and type(None) not in types:
return "required"
for t in types:
if t is type(None):
continue
if self._check_primitive(value, t) is None:
return None
names = [t.__name__ for t in types if t is not type(None)]
return f"expected {' or '.join(names)}"
def _check_primitive(self, value: Any, expected: Type) -> Optional[str]:
if expected == int and isinstance(value, bool) and self.strict_bool:
return "expected integer"
if isinstance(value, expected):
return None
return f"expected {expected.__name__}"
atc = AdvancedTypeChecker()
print(atc.check(None, (int, type(None)))) # nullable int -> None passes
print(atc.check("42", (int, str))) # union int|str
print(atc.check(42, (int, str))) # union int|str
print(atc.check(True, (int, type(None)))) # nullable int with bool
print(atc.check([], list)) # plain list
Expected output:
None
None
None
expected integer
None
Custom Type Validator
# custom_type.py
from typing import Any, Dict, List, Optional
class EmailType:
@staticmethod
def validate(value: Any) -> Optional[str]:
if not isinstance(value, str):
return "email must be a string"
if "@" not in value or "." not in value.split("@")[-1]:
return "invalid email format"
return None
class PositiveInt:
@staticmethod
def validate(value: Any) -> Optional[str]:
if not isinstance(value, int) or isinstance(value, bool):
return "must be an integer"
if value <= 0:
return "must be positive"
return None
class NonEmptyString:
@staticmethod
def validate(value: Any) -> Optional[str]:
if not isinstance(value, str):
return "must be a string"
if value.strip() == "":
return "cannot be empty"
return None
class CustomTypeValidator:
def __init__(self):
self.fields: Dict[str, Any] = {}
def add(self, field: str, type_validator: Any):
self.fields[field] = type_validator
def validate(self, data: Dict) -> List[Dict]:
errors = []
for field, validator in self.fields.items():
value = data.get(field)
if value is not None:
error = validator.validate(value)
if error:
errors.append({"field": field, "message": error})
return errors
cv = CustomTypeValidator()
cv.add("email", EmailType)
cv.add("count", PositiveInt)
cv.add("title", NonEmptyString)
data = {"email": "bad", "count": -5, "title": ""}
errors = cv.validate(data)
for e in errors:
print(f" {e}")
Expected output:
{'field': 'email', 'message': 'invalid email format'}
{'field': 'count', 'message': 'must be positive'}
{'field': 'title', 'message': 'cannot be empty'}
Common Mistakes
1. bool is int in Python
isinstance(True, int) returns True. Always check bool before int.
2. Not Handling None
None is a separate type. Required fields should reject None. Nullable fields should accept it.
3. Overly Strict Types
Accepting only int when float also makes sense. Accept string representations when appropriate.
4. No Type Coercion Before Type Check
HTTP values are strings. Coerce first, then type check.
5. Complex Type Logic in Validation Code
Use type hints and validation libraries instead of manual isinstance chains.
Practice Questions
1. Why is type checking important?
Type errors crash applications. Early type checking prevents NullPointerException and TypeError.
2. What is a union type?
A value that can be one of multiple types: int, float, or None.
3. How do you check for nullable types?
Accept the value if it matches the expected type OR is None.
4. What is the bool/int gotcha in Python?
bool is a subclass of int. isinstance(True, int) returns True. Check isinstance(v, bool) first.
Challenge
Build a type checking system that validates a product API: id (int), name (str), price (float), in_stock (bool), tags (list of strings), dimensions (nullable dict).
FAQ
Mini Project: Type Validator
# type_validator.py
from typing import Any, Dict, List
def type_check(schema: Dict[str, type], data: Dict) -> List[str]:
errors = []
for field, typ in schema.items():
val = data.get(field)
if val is not None and not isinstance(val, typ):
errors.append(f"{field}: expected {typ.__name__}")
return errors
s = {"name": str, "age": int, "active": bool}
print(type_check(s, {"name": "Alice", "age": "30", "active": True}))
print(type_check(s, {"name": "Alice", "age": 30, "active": True}))
Expected output:
['age: expected int']
[]
What's Next
You understand type checking. Next, learn format validation (email, URL), then range validation.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro