How to Validate Request Body JSON Against a Schema in Python
Build a lightweight schema validator to check required fields, types, string lengths, allowed values, and nested objects in a JSON request body.
Python code
71 linesimport json
def validate_against_schema(data, schema, path=""):
errors = []
if not isinstance(data, dict):
errors.append(f"{path}: expected object, got {type(data).__name__}")
return errors
for field, rules in schema.items():
field_path = f"{path}.{field}" if path else field
if field not in data:
if rules.get("required", False):
errors.append(f"{field_path}: missing required field")
continue
value = data[field]
expected_type = rules.get("type")
if expected_type:
if not isinstance(value, expected_type):
errors.append(
f"{field_path}: expected {expected_type.__name__}, got {type(value).__name__}"
)
continue
if expected_type is str and "min_length" in rules:
if len(value) < rules["min_length"]:
errors.append(
f"{field_path}: length {len(value)} < {rules['min_length']}"
)
if "allowed" in rules:
if value not in rules["allowed"]:
errors.append(f"{field_path}: value must be one of {rules['allowed']}")
if expected_type is dict and "properties" in rules:
nested_errors = validate_against_schema(
value, rules["properties"], field_path
)
errors.extend(nested_errors)
return errors
if __name__ == "__main__":
schema = {
"username": {"type": str, "required": True, "min_length": 3},
"email": {"type": str, "required": True},
"role": {"type": str, "required": True, "allowed": ["admin", "user", "guest"]},
"age": {"type": int, "required": False},
"address": {
"type": dict,
"required": False,
"properties": {
"city": {"type": str, "required": True},
"zip": {"type": str, "required": True}
}
}
}
request_body = {
"username": "alice",
"email": "alice@example.com",
"role": "admin"
}
result = validate_against_schema(request_body, schema)
print("Valid" if not result else "Errors: " + json.dumps(result))
Output
Valid
How it works
The function walks the schema recursively, tracking field paths for clear error messages. For each field, it checks presence, type, and optional constraints like min_length or allowed values. Nested dicts recurse into their own properties, accumulating errors across all levels. Using isinstance maps cleanly to Python's built-in types, so the schema reads like a plain dictionary instead of a third-party library.
Common mistakes
- Using json.loads on the body and then passing it directly without checking it is a dict first.
- Forgetting that booleans are subclasses of int, so isinstance(True, int) is True.
- Not skipping required checks when the field is absent from optional nested objects.
Variations
- Use the third-party jsonschema library with draft-07 JSON Schema syntax for standards-compliant validation.
- Validate with Pydantic models for automatic type coercion and nested schema handling.
Real-world use cases
- FastAPI or Flask route handlers validating incoming POST payloads before database insert.
- API gateway middleware rejecting malformed requests early in the request lifecycle.
- Internal microservice endpoints verifying event payloads before dispatching to event consumers.
Sponsored
More from API design & gRPC
- Build a Bulk Array POST Mock Server in Python medium
- Build a Mock REST API with PUT and GET in Python medium
- Convert Protobuf to JSON and Dict in Python easy
- Create a Data Helper in Python for gRPC-style APIs easy
- Format data in Python using dataclasses like gRPC messages easy
- Generate an OpenAPI Spec from Mock Routes in Python easy
Keep learning
Related tutorials and quizzes for this topic.