How to Use TypedDict for Data Validation in Python
Define a TypedDict schema and validate raw dictionary input with type hints for safer, more readable data handling.
Python code
38 linesfrom typing import Any, Dict, List, Optional, Union, TypedDict, Literal
class Product(TypedDict):
product_id: int
name: str
price: Union[int, float]
in_stock: bool
tags: Optional[List[str]]
def validate_product(data: Dict[str, Any]) -> Product:
product_id: int = int(data["product_id"])
name: str = str(data["name"])
price: Union[int, float] = data["price"]
in_stock: bool = bool(data["in_stock"])
tags: Optional[List[str]] = data.get("tags")
return {
"product_id": product_id,
"name": name,
"price": price,
"in_stock": in_stock,
"tags": tags,
}
def main() -> None:
raw_data: Dict[str, Any] = {
"product_id": 101,
"name": "Fidget Spinner",
"price": 4.99,
"in_stock": True,
"tags": ["toy", "fun"],
}
product: Product = validate_product(raw_data)
print(f"Validated product: {product}")
print(f"Name: {product['name']}, Price: {product['price']}")
print(f"In stock: {product['in_stock']}, Tags: {product['tags']}")
if __name__ == "__main__":
main()
Output
Validated product: {'product_id': 101, 'name': 'Fidget Spinner', 'price': 4.99, 'in_stock': True, 'tags': ['toy', 'fun']}
Name: Fidget Spinner, Price: 4.99
In stock: True, Tags: ['toy', 'fun']
How it works
TypedDict lets you define the exact shape and types of a dictionary at the type level, without runtime overhead. validate_product accepts a generic Dict[str, Any] and returns a Product, giving static checkers like mypy the ability to catch shape mismatches before they reach production. The explicit casts (int(), str(), bool()) handle raw values that come from JSON or an API where types may be loose. Using Optional[List[str]] for tags makes it clear that missing values are allowed. This pattern works well as a lightweight alternative to Pydantic when you prefer clever typing over third-party dependencies.
Common mistakes
- Using TypedDict as a runtime validator — it only helps static type checkers, not runtime checks.
- Forgetting to import `TypedDict` from `typing` (Python < 3.8) instead of the `typing_extensions` backport.
- Redefining the TypedDict class inside a function, which breaks type checking in some tools.
- Expecting `get()` with a default to preserve the declared Optional type — the default may need casing or a cast.
Variations
- Use `NotRequired` (from `typing_extensions` or Python 3.11+) to mark keys as optional rather than wrapping them in `Optional`.
- Return `TypedDict` directly from a cast of the raw data instead of rebuilding the dictionary manually.
Real-world use cases
- Validating and typing API response dictionaries before passing them into business logic or an ORM.
- Defining the shape of configuration files loaded at startup so misconfigured keys are caught by static analysis.
- Modeling record shapes in data pipelines where each row is a dictionary that must respect a contract.
Sponsored
More from Testing & modern typing
- Capture stdout and stderr with pytest capsys easy
- Characterization Test for Legacy Python Code medium
- Dataclass with Type Hints Fields in Python easy
- Dependency Injection in Python for Testability easy
- Design Data Helpers with Python TypedDict and Literal easy
- Fix and Test a Regression Bug in Python with Unit Tests easy
Keep learning
Related tutorials and quizzes for this topic.