Common Pydantic Schema Errors and How to Fix Them
The most frequent schema mistake is declaring a field with a
type that does not match the data it receives. A field typed
int that receives a float
string, or a field typed
str that receives a list,
will fail validation at the boundary — and the failure message
is your first clue. The generator's sample validation exists
precisely to surface these mismatches before you paste the model
into code: build the sample, hit validate, and read which field
and which type conflict. Fix the type declaration, not the data.
Missing defaults on optional fields is the classic silent trap. If your integration sends partial payloads but the model declares every field required, every partial payload fails. The reverse is equally common: a field that should be mandatory is given a default, so invalid payloads quietly succeed with placeholder data. Review requiredness with intent — a default exists to tolerate absence, not to paper over a data-quality problem.
Mutable defaults are a real bug in Python. Using a list or dict
literal directly as a default, such as
tags: list = [], shares
one mutable object across all instances, so one model's
append silently mutates
every other instance. The correct pattern is
Field(default_factory=list), which creates a fresh list per instance. The generator emits
the safe form automatically, but if you hand-edit the output,
keep default_factory for
any collection type.
Nullable fields expressed incorrectly are another recurring
failure. Declaring
Optional[str] signals the
field may be absent, but many teams mean "the value may be
null." When a producer sends
"name": null and the
model declares
Optional[str], Pydantic
accepts the null — which may be fine, or may be a silent
data-quality loss if downstream code assumes a string. Decide
deliberately, and when null is a valid value, type the field to
accept it and document why.
The
model_dump_json() versus
json.dumps() confusion
trips up anyone new to serialization. Pydantic models must be
dumped with
model_dump() or
model_dump_json(), not
passed directly to the standard library, or you get a
TypeError: Object of type ... is not JSON serializable. In Pydantic v2,
dict(model) also no
longer returns the validated data — another legacy habit that
quietly produces wrong output.
Field names that collide with Python or JSON keywords cause
subtle breakage. A field named
type,
schema, or
format can shadow
internals or be mangled in the generated JSON Schema. If you
must expose such names externally, use a field alias: name the
Python attribute something safe and declare
Field(alias="type") so
the wire format matches the producer while the codebase stays
clean. Aliases also bridge the camelCase-to-snake_case divide
when JSON uses one convention and Python the other.
Validator ordering mistakes produce confusing failures. A custom validator that assumes a field is already validated, when the field type check happens later, can dereference the wrong value or raise an obscure exception. Keep validators simple, validate one concern per validator, and remember that field-level validators run after type coercion — write them against the coerced value, not the raw input.
Finally, the schema drift error: the exported JSON Schema grows out of sync with the Python model after manual edits. Every time you change the model, regenerate the schema from the model rather than hand-editing the JSON. A stale schema is worse than no schema, because consumers trust it and validate against rules the code no longer enforces. The generator's side-by-side output makes the pairing easy to keep honest.
These failures share a root cause: the schema was written by assumption instead of validated against real data. The fix is mechanical — check each field's type, requiredness, nullability, defaults, aliases, and serialization path, then regenerate both artifacts and run the sample validation. A schema that has survived that review is a schema you can trust, and a model that validates real payloads is a model you can ship.
Nested models fail in a characteristic way: the outer validation
passes while the inner structure is subtly wrong. A field typed
as a nested model accepts any dict-like object and validates it
field by field — so a missing key inside the nested object fails
the whole model, and a wrong type inside the nested object
surfaces at the inner field, not the outer one. When a failure
message names a path like
address.city, the fix
belongs in the nested definition, not in the outer model. This
is also where inheritance mistakes appear: subclassing a model
and overriding a field's type to something incompatible can
produce validation behavior that surprises everyone. Keep nested
models independent and validate them in isolation with their own
test cases; then compose them, trusting that a tested inner
model rarely breaks the outer contract. The path-based error
messages are the map — follow them to the inner model that
actually owns the field.