{"record":{"id":"848b963a6af22ce5","repo":"pydantic/pydantic","slug":"expected-tp-but-got-type-path-with-value","errorCode":null,"errorMessage":"Expected `{tp}` but got `{type(path)}` with value `'{path}'` - 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":619,"sourceCode":"            try:\n                if path_type is bytes:\n                    if isinstance(input_value, bytes):\n                        try:\n                            input_value = input_value.decode()\n                        except UnicodeDecodeError as e:\n                            raise PydanticCustomError('bytes_type', 'Input must be valid bytes') from e\n                    else:\n                        raise PydanticCustomError('bytes_type', 'Input must be bytes')\n                elif not isinstance(input_value, str):\n                    raise PydanticCustomError('path_type', 'Input is not a valid path')\n\n                return path_constructor(input_value)  # type: ignore\n            except TypeError as e:\n                raise PydanticCustomError('path_type', 'Input is not a valid path') from e\n\n        def ser_path(path: Any, info: core_schema.SerializationInfo) -> str | os.PathLike[Any]:\n            if not isinstance(path, (tp, str)):\n                raise PydanticSerializationUnexpectedValue(\n                    f\"Expected `{tp}` but got `{type(path)}` with value `'{path}'` - serialized value may not be as expected.\"\n                )\n            if info.mode == 'python':\n                return path\n            return str(path)\n\n        instance_schema = core_schema.json_or_python_schema(\n            json_schema=core_schema.no_info_after_validator_function(path_validator, lax_inner_schema),\n            python_schema=core_schema.is_instance_schema(tp),\n        )\n\n        schema = core_schema.lax_or_strict_schema(\n            lax_schema=core_schema.union_schema(\n                [\n                    instance_schema,\n                    core_schema.no_info_after_validator_function(path_validator, strict_inner_schema),\n                ],\n                custom_error_type='path_type',","sourceCodeStart":601,"sourceCodeEnd":637,"githubUrl":"https://github.com/pydantic/pydantic/blob/cc13d1b8c978eaf78ed5308329cd41f03ecc3144/pydantic/_internal/_generate_schema.py#L601-L637","documentation":"PydanticSerializationUnexpectedValue raised in ser_path, the serializer pydantic attaches to path types. At serialization time it checks isinstance(path, (tp, str)); if the value held by the field is neither the declared path type (tp) nor a str, it raises this. Like error 42 for IPs, this indicates the field was populated with a value of the wrong type (e.g. via model_construct() or direct __dict__ mutation), so JSON serialization would emit an unexpected representation.","triggerScenarios":"model_construct(p=42) on a pathlib.Path field then model_dump_json(); assigning an int or dict to a path field after validation; a custom validator returning a non-path, non-str object; ORM integration that returns a raw column value into a path field without coercion.","commonSituations":"Using model_construct() for speed and feeding DB rows whose path column is None or an int; mutating the field post-validation; deserializing from msgpack/protobuf where the path is a bytes blob that was not decoded.","solutions":["Construct the model via Model.model_validate(data) instead of model_construct() so the path field is coerced to a real path object.","If model_construct() is required, pre-coerce: pathlib.Path(value) before assignment.","Make the field type a Union (pathlib.Path | None) and ensure None is handled in serialization.","Add a model_validator(mode='before') that normalizes raw values to paths."],"exampleFix":"// before\nm = Cfg.model_construct(p='/tmp/x')  # stored as str, not Path\nm.model_dump_json()  # may raise if tp is PurePath and value is str-only edge\n\n// after\nm = Cfg.model_validate({'p': '/tmp/x'})  # validated to PurePath\nm.model_dump_json()","handlingStrategy":"validation","validationCode":"import pathlib\n\ndef coerce_path(value):\n    if isinstance(value, (pathlib.PurePath, str)):\n        return value\n    if value is None:\n        return value\n    return pathlib.Path(value)\n\n# data['p'] = coerce_path(data['p']); then use model_validate, not model_construct","typeGuard":"import pathlib\n\ndef is_path_or_str(value, tp=pathlib.PurePath) -> bool:\n    return isinstance(value, (tp, str))","tryCatchPattern":"from pydantic_core import PydanticSerializationUnexpectedValue\n\ntry:\n    obj.model_dump_json()\nexcept PydanticSerializationUnexpectedValue:\n    obj = M.model_validate(obj.model_dump())  # re-coerce\n    obj.model_dump_json()","preventionTips":["Prefer model_validate() over model_construct() for models with path fields.","Add a model_validator(mode='before') that coerces raw values to paths.","Test JSON serialization on models constructed from raw input.","Document that path fields must hold path objects or strings."],"tags":["pydantic","serialization","path","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"}