Skip to content
Sarj Code Standards

require-pydantic-for-mapping-shape

Declare opaque mapping field shapes in Pydantic instead of probing them by hand.

Why

Fixed-key reads followed by runtime type, shape, or discriminator checks spread a record contract across control flow. An object/Any/JsonValue mapping does not declare those field contracts.

Fix

Parse the raw object with a named Pydantic BaseModel or RootModel before using typed attributes. Prefer strict fields, Field bounds, aliases/AliasPath and discriminated unions to handwritten probes. For an already trusted internal record, declare a precise named record such as TypedDict instead of parsing it again. Preserve rejection, defaults and fail-closed behavior explicitly; bool is an int subclass.

Examples

Before — flagged A match statement parses an opaque field by hand
app/claims.py
def verified(raw: dict[str, object]) -> bool:
match raw.get("factor_age"):
case [int(), int(age)] if not isinstance(age, bool):
return age >= 0
case _:
return False
After — preferred Strict fields and bounds declare the accepted shape
app/claims.py
from typing import Annotated
from pydantic import BaseModel, Field, StrictInt, ValidationError
class FactorClaim(BaseModel):
factor_age: tuple[StrictInt, Annotated[StrictInt, Field(ge=0)]]
def verified(raw: object) -> bool:
try:
FactorClaim.model_validate(raw)
except ValidationError:
return False
return True