pydantic/pydantic · error · PydanticSerializationUnexpectedValue
Expected ` ` but got ` ` with value `' '` - serialized…
Error message
Expected `{tp}` but got `{type(ip)}` with value `'{ip}'` - serialized value may not be as expected. What it means
PydanticSerializationUnexpectedValue raised inside ser_ip, the serializer pydantic attaches to ipaddress types (IPv4Address, IPv6Network, IPv4Interface, etc.). It fires at serialization time when model_dump(mode='json') (or any SerializationInfo path) calls ser_ip with a value that is neither an instance of the expected ip type (tp) nor a str. The serializer's contract is 'either the right IP object or a string fallback'; anything else means the field was assigned a wrong type, so serialization would produce an unexpected value.
Solutions
- Always construct the model via Model(**data) or Model.model_validate(data) so the IP field runs through IP_VALIDATOR_LOOKUP and becomes a real IP type.
- If using model_construct(), pre-coerce the value to the ipaddress type (ipaddress.ip_address(raw)) before assignment.
- Make the field type str | IPv4Address if arbitrary strings must be accepted, or store the raw value in a separate str field.
- In a custom serializer/validator, ensure the returned value is an instance of the declared IP type or a str.
Example fix
// before
m = MyModel.model_construct(ip=12345)
m.model_dump_json() # ser_ip: Expected IPv4Address but got int
// after
import ipaddress
m = MyModel.model_construct(ip=ipaddress.ip_address('1.2.3.4'))
m.model_dump_json() Defensive patterns
Strategy: validation
Validate before calling
import ipaddress
def coerce_ip(value, ip_type):
if isinstance(value, ip_type) or isinstance(value, str):
return value
# validate eagerly so the error is a ValidationError, not a serialization surprise
return ip_type(value)
# data['ip'] = coerce_ip(data['ip'], ipaddress.IPv4Address) before model_validate Type guard
import ipaddress
def is_ip_or_str(value, ip_type) -> bool:
return isinstance(value, (ip_type, str)) Try / catch
from pydantic_core import PydanticSerializationUnexpectedValue
try:
obj.model_dump_json()
except PydanticSerializationUnexpectedValue as e:
# re-validate the offending field via model_validate, then retry serialization
obj = MyModel.model_validate(obj.model_dump())
raise Prevention
- Avoid model_construct() for models with IP fields; use model_validate().
- Validate at the input boundary so fields hold real IP objects.
- Add a serializer test that dumps a freshly-validated model.
- Document that IP fields must receive IP objects or strings.
When it happens
Trigger: Assigning an int, float, dict, or unrelated object to a model field typed IPv4Address and then calling model_dump_json(); building a model with model_construct() (skipping validation) and putting a non-IP value in an IP field; a custom validator that returns a non-IP, non-str value; serializing in 'json' mode when the field was populated by external code that bypassed pydantic validation.
Common situations: Using model_construct() for performance and feeding raw data straight from a DB row whose column is a string that is not a valid IP; mixing validated and unvalidated construction paths; a field declared IPv4Address but populated by an ORM relationship returning a numeric ID; concurrent mutation of the field after validation.
Related errors
- Expected ` ` but got ` ` with value `' '` - serialized…
- Excluding fields from a sequence of sub-models or dicts…
- Expected ` ` but got ` ` with value `' '` - serialized…
- Unexpected type of exclude value for index
- ip_v4_address
AI-assisted analysis of pydantic/pydantic@cc13d1b8c9 (2026-08-11).
Data as JSON: /api/errors/869b5d1cb6e4145d.
Report an issue: GitHub.
Appendix: source
Thrown at pydantic/_internal/_generate_schema.py:569
enum_type,
metadata={'pydantic_js_functions': [get_json_schema_no_cases]},
)
def _ip_schema(self, tp: Any) -> CoreSchema:
from ._validators import IP_VALIDATOR_LOOKUP, IpType
ip_type_json_schema_format: dict[type[IpType], str] = {
IPv4Address: 'ipv4',
IPv4Network: 'ipv4network',
IPv4Interface: 'ipv4interface',
IPv6Address: 'ipv6',
IPv6Network: 'ipv6network',
IPv6Interface: 'ipv6interface',
}
def ser_ip(ip: Any, info: core_schema.SerializationInfo) -> str | IpType:
if not isinstance(ip, (tp, str)):
raise PydanticSerializationUnexpectedValue(
f"Expected `{tp}` but got `{type(ip)}` with value `'{ip}'` - serialized value may not be as expected."
)
if info.mode == 'python':
return ip
return str(ip)
return core_schema.lax_or_strict_schema(
lax_schema=core_schema.no_info_plain_validator_function(IP_VALIDATOR_LOOKUP[tp]),
strict_schema=core_schema.json_or_python_schema(
json_schema=core_schema.no_info_after_validator_function(tp, core_schema.str_schema()),
python_schema=core_schema.is_instance_schema(tp),
),
serialization=core_schema.plain_serializer_function_ser_schema(ser_ip, info_arg=True, when_used='always'),
metadata={
'pydantic_js_functions': [lambda _1, _2: {'type': 'string', 'format': ip_type_json_schema_format[tp]}]
},
)
View on GitHub (pinned to cc13d1b8c9)