Pydantic validation and serialization
Produce version-grounded boundaries whose input source, conversion policy,
validated output type, invariants, and serialized shape are explicit and tested.
Boundary
Use this skill when the project uses Pydantic or the user
explicitly requests them. Do not introduce runtime validation for a trusted
internal record that only needs a dataclass or TypedDict. Route BaseSettings,
environment, dotenv, secrets-directory, or settings-source work to the
pydantic-settings-python skill. Preserve public validation errors and
serialized schemas unless the task explicitly changes them.
Know the two compiled paths
Pydantic builds a core schema from annotations, field metadata, configuration,
and decorators. Validation and serialization use that schema but are different
contracts:
Python / JSON / string input -> validator -> typed value or ValidationError
typed value -> serializer (python or json mode) -> dict / JSON-compatible data / JSON
| Object |
Runtime meaning |
Use it for |
BaseModel class |
A named object schema plus class validation/JSON Schema APIs. |
Domain or boundary objects with named fields. |
| Model instance |
Validated typed state; not the original input mapping. |
Internal typed work and explicit serialization. |
Field / Annotated metadata |
Constraints, defaults, aliases, exclusion, and schema details attached to a type. |
Rules expressible without custom code; reusable constrained types. |
| Field/model validator |
User code inserted before, after, plain, or around core validation. |
Only invariants or normalization the schema cannot express clearly. |
| Field/model serializer |
User code inserted into output conversion. |
Output rules that differ from validation; never use validators as serializers. |
TypeAdapter[T] |
A compiled validator/serializer for any supported type T without an artificial model. |
Collections, unions, TypedDict, dataclasses, and standalone types. Reuse it. |
RootModel[T] |
A named model whose payload is one root value. |
A true root-value public type that needs model behavior. |
Read the core object model when choosing an
abstraction or reasoning about validation versus serialization.
Ordered workflow
- Recover the boundary: input source and shape, trust, desired output type,
accepted coercions, extra-data policy, aliases, and serialized contract.
- Confirm the installed Pydantic version from project locks and the active environment.
- Choose
BaseModel, TypeAdapter, or RootModel from the
required runtime object—not from habit.
- Express structure and constraints in types/fields. Add the narrowest
deterministic validator only for remaining rules.
- Choose the entrypoint matching Python, JSON, or string-mapping input.
- Design serialization separately: mode, aliases, exclusions, subclass policy,
and sensitive fields.
- Test valid, invalid, conversion, extra-key, alias, invariant, and serialized
cases.
Choose by intent
| Intent |
Use |
| Validate a Python mapping/object as a model |
Model.model_validate(...) |
| Validate JSON text/bytes as a model |
Model.model_validate_json(...) |
| Validate a nested string-key/string-value mapping in JSON mode |
Model.model_validate_strings(...) only when that source contract fits |
Validate list[Item], a union, TypedDict, or another standalone type |
Create and reuse TypeAdapter(type) |
| Represent a named single root payload |
RootModel[T] |
| Constrain one field |
Annotated[T, Field(...)] or Field(...) |
| Reuse a custom constraint/normalizer |
A named Annotated type with functional metadata |
| Select one tagged variant predictably |
A discriminated union with Literal tags and Field(discriminator=...) |
| Normalize raw input before typing |
a mode="before" validator that accepts arbitrary input safely |
| Enforce a typed field rule |
an after field validator |
| Enforce a cross-field invariant |
an after model validator |
| Emit Python-native objects |
model_dump(mode="python") (the default) |
| Emit JSON-compatible Python values |
model_dump(mode="json") |
| Emit JSON text |
model_dump_json() |
| Use external names on input only |
validation_alias |
| Use external names on output only |
serialization_alias plus by_alias=True |
Read the intent-to-API map for aliases, unions,
attribute loading, JSON Schema, dataclasses, call validation, and dynamic models.
Canonical strict boundary
from decimal import Decimal
from typing import Annotated, Literal
from pydantic import BaseModel, ConfigDict, Field, TypeAdapter
PositiveMoney = Annotated[Decimal, Field(gt=0, strict=True)]
class CardPayment(BaseModel):
model_config = ConfigDict(extra="forbid", strict=True)
kind: Literal["card"]
amount: PositiveMoney
currency: str = Field(pattern=r"^[A-Z]{3}$")
payments = TypeAdapter(list[CardPayment])
validated = payments.validate_python(payload)
wire_value = payments.dump_python(validated, mode="json")
Pydantic may coerce in lax mode, sometimes with information loss. Strictness can
be chosen per call, field, or configuration, and JSON strict behavior can differ
from Python strict behavior. Test the actual input modes; do not infer one from
the other. Read validation boundaries.
Validator rules
- Prefer types,
Field constraints, tagged unions, and configuration over
validators. They compose and generate schemas more predictably.
- A before validator receives arbitrary raw input; do not assume its type or
mutate a value that may later flow to another union branch.
- An after validator receives the typed value. Return the value/model on every
success path.
- Use a model validator only for genuinely cross-field invariants. Do not rely
on field order for cross-field logic.
- Raise documented validation failures such as
ValueError for bad user data.
Keep I/O, database access, clocks, randomness, and remote lookups outside.
- Catch
ValidationError at the boundary that can translate it. Assert stable
errors() fields needed by callers, not whole human-formatted messages.
Serialization is a separate contract
- Choose Python mode, JSON mode, or JSON text deliberately.
dict(model) leaves
nested model objects intact; it is not a substitute for model_dump().
- Name input and output aliases independently.
alias affects both directions;
validation_alias and serialization_alias express asymmetric contracts.
- Use
exclude_unset, exclude_defaults, or exclude_none only when that
omission policy is part of the wire contract and is tested.
- By default, a field annotated as a base model serializes fields declared on
that annotation, limiting accidental subclass-secret exposure. Treat any
serialize-as-runtime-type option as security-sensitive and version-ground it.
- Add serializers only when the output rule cannot be expressed by normal
modes/configuration. Test their return shape; serialization does not revalidate
arbitrary post-construction mutation.
Read serialization and aliases.
Version grounding and completion
If code uses v1-shaped parse_obj, .dict(), .json(), @validator,
@root_validator, class Config, or orm_mode, inspect the declared version
and read v1-to-v2 grounding. Do not mechanically rename:
optional-field requirements, aliases, equality, attribute loading, validators,
and serialization can change.
Run python scripts/inspect_pydantic.py from the installed skill directory for
installed versions, API availability, and signatures. Read API grounding.
Do not declare completion until input mode, output type, coercion, extras,
aliases, invariants, and serialization are tested; sensitive fields cannot leak; validation bypasses
are absent from untrusted paths; version-supported APIs are used; and project
checks pass or skipped evidence and consequences are reported. Use the testing
matrix.
References
- Validation and serialization recipes
- Core object model
- Intent-to-API map
- Validation boundaries
- Serialization and aliases
- V1-to-v2 grounding
- Testing matrix
- API grounding
1---2name: pydantic-python3description: Write, review, debug, migrate, or test Python code using Pydantic v2 with explicit boundaries, validation, and serialization contracts.4---56# Pydantic validation and serialization78Produce version-grounded boundaries whose input source, conversion policy,9validated output type, invariants, and serialized shape are explicit and tested.1011## Boundary1213Use this skill when the project uses Pydantic or the user14explicitly requests them. Do not introduce runtime validation for a trusted15internal record that only needs a dataclass or `TypedDict`. Route BaseSettings,16environment, dotenv, secrets-directory, or settings-source work to the17`pydantic-settings-python` skill. Preserve public validation errors and18serialized schemas unless the task explicitly changes them.1920## Know the two compiled paths2122Pydantic builds a core schema from annotations, field metadata, configuration,23and decorators. Validation and serialization use that schema but are different24contracts:2526```text27Python / JSON / string input -> validator -> typed value or ValidationError28typed value -> serializer (python or json mode) -> dict / JSON-compatible data / JSON29```3031| Object | Runtime meaning | Use it for |32|---|---|---|33| `BaseModel` class | A named object schema plus class validation/JSON Schema APIs. | Domain or boundary objects with named fields. |34| Model instance | Validated typed state; not the original input mapping. | Internal typed work and explicit serialization. |35| `Field` / `Annotated` metadata | Constraints, defaults, aliases, exclusion, and schema details attached to a type. | Rules expressible without custom code; reusable constrained types. |36| Field/model validator | User code inserted before, after, plain, or around core validation. | Only invariants or normalization the schema cannot express clearly. |37| Field/model serializer | User code inserted into output conversion. | Output rules that differ from validation; never use validators as serializers. |38| `TypeAdapter[T]` | A compiled validator/serializer for any supported type `T` without an artificial model. | Collections, unions, `TypedDict`, dataclasses, and standalone types. Reuse it. |39| `RootModel[T]` | A named model whose payload is one root value. | A true root-value public type that needs model behavior. |4041Read [the core object model](references/object-model.md) when choosing an42abstraction or reasoning about validation versus serialization.4344## Ordered workflow45461. Recover the boundary: input source and shape, trust, desired output type,47 accepted coercions, extra-data policy, aliases, and serialized contract.482. Confirm the installed Pydantic version from project locks and the active environment.493. Choose `BaseModel`, `TypeAdapter`, or `RootModel` from the50 required runtime object—not from habit.514. Express structure and constraints in types/fields. Add the narrowest52 deterministic validator only for remaining rules.535. Choose the entrypoint matching Python, JSON, or string-mapping input.546. Design serialization separately: mode, aliases, exclusions, subclass policy,55 and sensitive fields.567. Test valid, invalid, conversion, extra-key, alias, invariant, and serialized57 cases.5859## Choose by intent6061| Intent | Use |62|---|---|63| Validate a Python mapping/object as a model | `Model.model_validate(...)` |64| Validate JSON text/bytes as a model | `Model.model_validate_json(...)` |65| Validate a nested string-key/string-value mapping in JSON mode | `Model.model_validate_strings(...)` only when that source contract fits |66| Validate `list[Item]`, a union, `TypedDict`, or another standalone type | Create and reuse `TypeAdapter(type)` |67| Represent a named single root payload | `RootModel[T]` |68| Constrain one field | `Annotated[T, Field(...)]` or `Field(...)` |69| Reuse a custom constraint/normalizer | A named `Annotated` type with functional metadata |70| Select one tagged variant predictably | A discriminated union with `Literal` tags and `Field(discriminator=...)` |71| Normalize raw input before typing | a `mode="before"` validator that accepts arbitrary input safely |72| Enforce a typed field rule | an after field validator |73| Enforce a cross-field invariant | an after model validator |74| Emit Python-native objects | `model_dump(mode="python")` (the default) |75| Emit JSON-compatible Python values | `model_dump(mode="json")` |76| Emit JSON text | `model_dump_json()` |77| Use external names on input only | `validation_alias` |78| Use external names on output only | `serialization_alias` plus `by_alias=True` |7980Read [the intent-to-API map](references/api-map.md) for aliases, unions,81attribute loading, JSON Schema, dataclasses, call validation, and dynamic models.8283## Canonical strict boundary8485```python86from decimal import Decimal87from typing import Annotated, Literal8889from pydantic import BaseModel, ConfigDict, Field, TypeAdapter909192PositiveMoney = Annotated[Decimal, Field(gt=0, strict=True)]939495class CardPayment(BaseModel):96 model_config = ConfigDict(extra="forbid", strict=True)9798 kind: Literal["card"]99 amount: PositiveMoney100 currency: str = Field(pattern=r"^[A-Z]{3}$")101102103payments = TypeAdapter(list[CardPayment])104validated = payments.validate_python(payload)105wire_value = payments.dump_python(validated, mode="json")106```107108Pydantic may coerce in lax mode, sometimes with information loss. Strictness can109be chosen per call, field, or configuration, and JSON strict behavior can differ110from Python strict behavior. Test the actual input modes; do not infer one from111the other. Read [validation boundaries](references/validation.md).112113## Validator rules114115- Prefer types, `Field` constraints, tagged unions, and configuration over116 validators. They compose and generate schemas more predictably.117- A before validator receives arbitrary raw input; do not assume its type or118 mutate a value that may later flow to another union branch.119- An after validator receives the typed value. Return the value/model on every120 success path.121- Use a model validator only for genuinely cross-field invariants. Do not rely122 on field order for cross-field logic.123- Raise documented validation failures such as `ValueError` for bad user data.124 Keep I/O, database access, clocks, randomness, and remote lookups outside.125- Catch `ValidationError` at the boundary that can translate it. Assert stable126 `errors()` fields needed by callers, not whole human-formatted messages.127128## Serialization is a separate contract129130- Choose Python mode, JSON mode, or JSON text deliberately. `dict(model)` leaves131 nested model objects intact; it is not a substitute for `model_dump()`.132- Name input and output aliases independently. `alias` affects both directions;133 `validation_alias` and `serialization_alias` express asymmetric contracts.134- Use `exclude_unset`, `exclude_defaults`, or `exclude_none` only when that135 omission policy is part of the wire contract and is tested.136- By default, a field annotated as a base model serializes fields declared on137 that annotation, limiting accidental subclass-secret exposure. Treat any138 serialize-as-runtime-type option as security-sensitive and version-ground it.139- Add serializers only when the output rule cannot be expressed by normal140 modes/configuration. Test their return shape; serialization does not revalidate141 arbitrary post-construction mutation.142143Read [serialization and aliases](references/serialization.md).144145## Version grounding and completion146147If code uses v1-shaped `parse_obj`, `.dict()`, `.json()`, `@validator`,148`@root_validator`, `class Config`, or `orm_mode`, inspect the declared version149and read [v1-to-v2 grounding](references/v1-v2.md). Do not mechanically rename:150optional-field requirements, aliases, equality, attribute loading, validators,151and serialization can change.152153Run `python scripts/inspect_pydantic.py` from the installed skill directory for154installed versions, API availability, and signatures. Read [API grounding](references/api-grounding.md).155156Do not declare completion until input mode, output type, coercion, extras,157aliases, invariants, and serialization are tested; sensitive fields cannot leak; validation bypasses158are absent from untrusted paths; version-supported APIs are used; and project159checks pass or skipped evidence and consequences are reported. Use [the testing160matrix](references/testing.md).161162## References163164- [Validation and serialization recipes](references/recipes-contracts.md)165- [Core object model](references/object-model.md)166- [Intent-to-API map](references/api-map.md)167- [Validation boundaries](references/validation.md)168- [Serialization and aliases](references/serialization.md)169- [V1-to-v2 grounding](references/v1-v2.md)170- [Testing matrix](references/testing.md)171- [API grounding](references/api-grounding.md)