{"record":{"id":"869b5d1cb6e4145d","repo":"pydantic/pydantic","slug":"expected-tp-but-got-type-ip-with-value","errorCode":null,"errorMessage":"Expected `{tp}` but got `{type(ip)}` with value `'{ip}'` - serialized value may not be as expected.","messagePattern":"Expected `(.+?)` but got `(.+?)` with value `'(.+?)'` - serialized value may not be as expected\\.","errorType":"exception","errorClass":"PydanticSerializationUnexpectedValue","httpStatus":null,"severity":"error","filePath":"pydantic/_internal/_generate_schema.py","lineNumber":569,"sourceCode":"                enum_type,\n                metadata={'pydantic_js_functions': [get_json_schema_no_cases]},\n            )\n\n    def _ip_schema(self, tp: Any) -> CoreSchema:\n        from ._validators import IP_VALIDATOR_LOOKUP, IpType\n\n        ip_type_json_schema_format: dict[type[IpType], str] = {\n            IPv4Address: 'ipv4',\n            IPv4Network: 'ipv4network',\n            IPv4Interface: 'ipv4interface',\n            IPv6Address: 'ipv6',\n            IPv6Network: 'ipv6network',\n            IPv6Interface: 'ipv6interface',\n        }\n\n        def ser_ip(ip: Any, info: core_schema.SerializationInfo) -> str | IpType:\n            if not isinstance(ip, (tp, str)):\n                raise PydanticSerializationUnexpectedValue(\n                    f\"Expected `{tp}` but got `{type(ip)}` with value `'{ip}'` - serialized value may not be as expected.\"\n                )\n            if info.mode == 'python':\n                return ip\n            return str(ip)\n\n        return core_schema.lax_or_strict_schema(\n            lax_schema=core_schema.no_info_plain_validator_function(IP_VALIDATOR_LOOKUP[tp]),\n            strict_schema=core_schema.json_or_python_schema(\n                json_schema=core_schema.no_info_after_validator_function(tp, core_schema.str_schema()),\n                python_schema=core_schema.is_instance_schema(tp),\n            ),\n            serialization=core_schema.plain_serializer_function_ser_schema(ser_ip, info_arg=True, when_used='always'),\n            metadata={\n                'pydantic_js_functions': [lambda _1, _2: {'type': 'string', 'format': ip_type_json_schema_format[tp]}]\n            },\n        )\n","sourceCodeStart":551,"sourceCodeEnd":587,"githubUrl":"https://github.com/pydantic/pydantic/blob/cc13d1b8c978eaf78ed5308329cd41f03ecc3144/pydantic/_internal/_generate_schema.py#L551-L587","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nm = MyModel.model_construct(ip=12345)\nm.model_dump_json()  # ser_ip: Expected IPv4Address but got int\n\n// after\nimport ipaddress\nm = MyModel.model_construct(ip=ipaddress.ip_address('1.2.3.4'))\nm.model_dump_json()","handlingStrategy":"validation","validationCode":"import ipaddress\n\ndef coerce_ip(value, ip_type):\n    if isinstance(value, ip_type) or isinstance(value, str):\n        return value\n    # validate eagerly so the error is a ValidationError, not a serialization surprise\n    return ip_type(value)\n\n# data['ip'] = coerce_ip(data['ip'], ipaddress.IPv4Address) before model_validate","typeGuard":"import ipaddress\n\ndef is_ip_or_str(value, ip_type) -> bool:\n    return isinstance(value, (ip_type, str))","tryCatchPattern":"from pydantic_core import PydanticSerializationUnexpectedValue\n\ntry:\n    obj.model_dump_json()\nexcept PydanticSerializationUnexpectedValue as e:\n    # re-validate the offending field via model_validate, then retry serialization\n    obj = MyModel.model_validate(obj.model_dump())\n    raise","preventionTips":["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."],"tags":["pydantic","serialization","ipaddress","model-construct","type-mismatch"],"backgroundTag":null,"analyzedSha":"cc13d1b8c978eaf78ed5308329cd41f03ecc3144","analyzedAt":"2026-08-11T16:38:52.905Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}