[{"data":1,"prerenderedAt":2441},["ShallowReactive",2],{"nav":3,"page-\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Freplacing-json-encoders-with-field-serializer\u002F":580,"surround-\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Freplacing-json-encoders-with-field-serializer\u002F":2440},[4,186,386],{"title":5,"path":6,"stem":7,"children":8},"Advanced Pydantic Validation Serialization","\u002Fadvanced-pydantic-validation-serialization","advanced-pydantic-validation-serialization",[9,12,42,66,90,114,150,174],{"title":10,"path":6,"stem":11},"Advanced Pydantic Validation and Serialization","advanced-pydantic-validation-serialization\u002Findex",{"title":13,"path":14,"stem":15,"children":16},"Custom Validators and Field Constraints in Pydantic","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Findex",[17,18,24,30,36],{"title":13,"path":14,"stem":15},{"title":19,"path":20,"stem":21,"children":22},"Before, After and Wrap Validators in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fbefore-after-and-wrap-validators","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fbefore-after-and-wrap-validators\u002Findex",[23],{"title":19,"path":20,"stem":21},{"title":25,"path":26,"stem":27,"children":28},"Creating Reusable Custom Validators in Pydantic","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fcreating-reusable-custom-validators-in-pydantic","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fcreating-reusable-custom-validators-in-pydantic\u002Findex",[29],{"title":25,"path":26,"stem":27},{"title":31,"path":32,"stem":33,"children":34},"Cross-Field Validation Patterns in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fcross-field-validation-patterns","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fcross-field-validation-patterns\u002Findex",[35],{"title":31,"path":32,"stem":33},{"title":37,"path":38,"stem":39,"children":40},"Pydantic v2 Async Custom Validator: What to Do Instead","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fpydantic-v2-async-custom-validator","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fpydantic-v2-async-custom-validator\u002Findex",[41],{"title":37,"path":38,"stem":39},{"title":43,"path":44,"stem":45,"children":46},"JSON Schema Customization in Pydantic and FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fjson-schema-customization","advanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Findex",[47,48,54,60],{"title":43,"path":44,"stem":45},{"title":49,"path":50,"stem":51,"children":52},"Customizing OpenAPI Schema Generation in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fcustomizing-openapi-schema-generation-in-fastapi","advanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fcustomizing-openapi-schema-generation-in-fastapi\u002Findex",[53],{"title":49,"path":50,"stem":51},{"title":55,"path":56,"stem":57,"children":58},"Discriminated Unions in OpenAPI with Pydantic","\u002Fadvanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fdiscriminated-unions-in-openapi","advanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fdiscriminated-unions-in-openapi\u002Findex",[59],{"title":55,"path":56,"stem":57},{"title":61,"path":62,"stem":63,"children":64},"Examples in the OpenAPI Schema with FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fexamples-in-openapi-schema","advanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fexamples-in-openapi-schema\u002Findex",[65],{"title":61,"path":62,"stem":63},{"title":67,"path":68,"stem":69,"children":70},"Nested Model Serialization in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fnested-model-serialization","advanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Findex",[71,72,78,84],{"title":67,"path":68,"stem":69},{"title":73,"path":74,"stem":75,"children":76},"Excluding Fields Per Endpoint in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fexcluding-fields-per-endpoint","advanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fexcluding-fields-per-endpoint\u002Findex",[77],{"title":73,"path":74,"stem":75},{"title":79,"path":80,"stem":81,"children":82},"Handling Deeply Nested JSON Models Efficiently","\u002Fadvanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fhandling-deeply-nested-json-models-efficiently","advanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fhandling-deeply-nested-json-models-efficiently\u002Findex",[83],{"title":79,"path":80,"stem":81},{"title":85,"path":86,"stem":87,"children":88},"Self-Referencing and Recursive Models in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fself-referencing-and-recursive-models","advanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fself-referencing-and-recursive-models\u002Findex",[89],{"title":85,"path":86,"stem":87},{"title":91,"path":92,"stem":93,"children":94},"Performance Optimization for Pydantic Models in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models","advanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Findex",[95,96,102,108],{"title":91,"path":92,"stem":93},{"title":97,"path":98,"stem":99,"children":100},"model_construct and When to Skip Validation in Pydantic","\u002Fadvanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Fmodel-construct-when-to-skip-validation","advanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Fmodel-construct-when-to-skip-validation\u002Findex",[101],{"title":97,"path":98,"stem":99},{"title":103,"path":104,"stem":105,"children":106},"Pydantic Model Serialization Performance in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Fpydantic-model-serialization-performance","advanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Fpydantic-model-serialization-performance\u002Findex",[107],{"title":103,"path":104,"stem":105},{"title":109,"path":110,"stem":111,"children":112},"TypeAdapter for Non-Model Types in Pydantic","\u002Fadvanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Ftypeadapter-for-non-model-types","advanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Ftypeadapter-for-non-model-types\u002Findex",[113],{"title":109,"path":110,"stem":111},{"title":115,"path":116,"stem":117,"children":118},"Pydantic V2 Migration Guide for FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Findex",[119,120,126,132,138,144],{"title":115,"path":116,"stem":117},{"title":121,"path":122,"stem":123,"children":124},"Migrate @validator to @field_validator in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrate-validator-to-field-validator","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrate-validator-to-field-validator\u002Findex",[125],{"title":121,"path":122,"stem":123},{"title":127,"path":128,"stem":129,"children":130},"Migrating from Pydantic v1 to v2 Without Breaking APIs","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrating-from-pydantic-v1-to-v2-without-breaking-apis","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrating-from-pydantic-v1-to-v2-without-breaking-apis\u002Findex",[131],{"title":127,"path":128,"stem":129},{"title":133,"path":134,"stem":135,"children":136},"model_config vs class Config in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmodel-config-vs-class-config","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmodel-config-vs-class-config\u002Findex",[137],{"title":133,"path":134,"stem":135},{"title":139,"path":140,"stem":141,"children":142},"Replacing json_encoders with field_serializer in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Freplacing-json-encoders-with-field-serializer","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Freplacing-json-encoders-with-field-serializer\u002Findex",[143],{"title":139,"path":140,"stem":141},{"title":145,"path":146,"stem":147,"children":148},"Migrating @root_validator to @model_validator in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Froot-validator-to-model-validator","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Froot-validator-to-model-validator\u002Findex",[149],{"title":145,"path":146,"stem":147},{"title":151,"path":152,"stem":153,"children":154},"Request Validation Patterns in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Frequest-validation-patterns","advanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Findex",[155,156,162,168],{"title":151,"path":152,"stem":153},{"title":157,"path":158,"stem":159,"children":160},"Optional vs Nullable Fields in Pydantic and FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Foptional-vs-nullable-fields","advanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Foptional-vs-nullable-fields\u002Findex",[161],{"title":157,"path":158,"stem":159},{"title":163,"path":164,"stem":165,"children":166},"Query, Path and Body Parameter Validation in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Fquery-path-and-body-parameter-validation","advanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Fquery-path-and-body-parameter-validation\u002Findex",[167],{"title":163,"path":164,"stem":165},{"title":169,"path":170,"stem":171,"children":172},"Validating File Uploads and Forms in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Fvalidating-file-uploads-and-forms","advanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Fvalidating-file-uploads-and-forms\u002Findex",[173],{"title":169,"path":170,"stem":171},{"title":175,"path":176,"stem":177,"children":178},"Type Hinting and IDE Integration in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Ftype-hinting-ide-integration","advanced-pydantic-validation-serialization\u002Ftype-hinting-ide-integration\u002Findex",[179,180],{"title":175,"path":176,"stem":177},{"title":181,"path":182,"stem":183,"children":184},"Annotated Dependencies and Reusable Types in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Ftype-hinting-ide-integration\u002Fannotated-dependencies-and-reusable-types","advanced-pydantic-validation-serialization\u002Ftype-hinting-ide-integration\u002Fannotated-dependencies-and-reusable-types\u002Findex",[185],{"title":181,"path":182,"stem":183},{"title":187,"path":188,"stem":189,"children":190},"Async Background Tasks Observability","\u002Fasync-background-tasks-observability","async-background-tasks-observability",[191,194,224,254,284,308,338,362],{"title":192,"path":188,"stem":193},"Async, Background Tasks, and Observability in FastAPI","async-background-tasks-observability\u002Findex",{"title":195,"path":196,"stem":197,"children":198},"Async Correctness and Concurrency in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Findex",[199,200,206,212,218],{"title":195,"path":196,"stem":197},{"title":201,"path":202,"stem":203,"children":204},"Concurrent Requests with asyncio.gather in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Fconcurrent-requests-with-asyncio-gather","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Fconcurrent-requests-with-asyncio-gather\u002Findex",[205],{"title":201,"path":202,"stem":203},{"title":207,"path":208,"stem":209,"children":210},"FastAPI async def vs def: Performance and When to Use Each","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffastapi-async-def-vs-def-performance","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffastapi-async-def-vs-def-performance\u002Findex",[211],{"title":207,"path":208,"stem":209},{"title":213,"path":214,"stem":215,"children":216},"Fixing Blocking Calls in Async FastAPI Routes","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffixing-blocking-calls-in-async-routes","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffixing-blocking-calls-in-async-routes\u002Findex",[217],{"title":213,"path":214,"stem":215},{"title":219,"path":220,"stem":221,"children":222},"Running Sync Code in a Threadpool in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Frunning-sync-code-in-a-threadpool","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Frunning-sync-code-in-a-threadpool\u002Findex",[223],{"title":219,"path":220,"stem":221},{"title":225,"path":226,"stem":227,"children":228},"Async Database Sessions in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions","async-background-tasks-observability\u002Fasync-database-sessions\u002Findex",[229,230,236,242,248],{"title":225,"path":226,"stem":227},{"title":231,"path":232,"stem":233,"children":234},"Async SQLAlchemy Session per Request in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Fasync-sqlalchemy-session-per-request","async-background-tasks-observability\u002Fasync-database-sessions\u002Fasync-sqlalchemy-session-per-request\u002Findex",[235],{"title":231,"path":232,"stem":233},{"title":237,"path":238,"stem":239,"children":240},"Fixing asyncpg Connection Pool Exhaustion in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Ffixing-asyncpg-pool-exhaustion","async-background-tasks-observability\u002Fasync-database-sessions\u002Ffixing-asyncpg-pool-exhaustion\u002Findex",[241],{"title":237,"path":238,"stem":239},{"title":243,"path":244,"stem":245,"children":246},"Testing with Async Database Fixtures in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Ftesting-with-async-database-fixtures","async-background-tasks-observability\u002Fasync-database-sessions\u002Ftesting-with-async-database-fixtures\u002Findex",[247],{"title":243,"path":244,"stem":245},{"title":249,"path":250,"stem":251,"children":252},"Transaction Management and Rollback in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Ftransaction-management-and-rollback","async-background-tasks-observability\u002Fasync-database-sessions\u002Ftransaction-management-and-rollback\u002Findex",[253],{"title":249,"path":250,"stem":251},{"title":255,"path":256,"stem":257,"children":258},"Background Task Processing in FastAPI","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing","async-background-tasks-observability\u002Fbackground-task-processing\u002Findex",[259,260,266,272,278],{"title":255,"path":256,"stem":257},{"title":261,"path":262,"stem":263,"children":264},"FastAPI BackgroundTasks vs Celery vs ARQ","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Ffastapi-backgroundtasks-vs-celery-vs-arq","async-background-tasks-observability\u002Fbackground-task-processing\u002Ffastapi-backgroundtasks-vs-celery-vs-arq\u002Findex",[265],{"title":261,"path":262,"stem":263},{"title":267,"path":268,"stem":269,"children":270},"Retry and Idempotency for FastAPI Background Tasks","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Fretry-and-idempotency-for-tasks","async-background-tasks-observability\u002Fbackground-task-processing\u002Fretry-and-idempotency-for-tasks\u002Findex",[271],{"title":267,"path":268,"stem":269},{"title":273,"path":274,"stem":275,"children":276},"Running ARQ Workers with FastAPI","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Frunning-arq-workers-with-fastapi","async-background-tasks-observability\u002Fbackground-task-processing\u002Frunning-arq-workers-with-fastapi\u002Findex",[277],{"title":273,"path":274,"stem":275},{"title":279,"path":280,"stem":281,"children":282},"When FastAPI BackgroundTasks Silently Fails","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Fwhen-backgroundtasks-silently-fails","async-background-tasks-observability\u002Fbackground-task-processing\u002Fwhen-backgroundtasks-silently-fails\u002Findex",[283],{"title":279,"path":280,"stem":281},{"title":285,"path":286,"stem":287,"children":288},"Caching Strategies in FastAPI","\u002Fasync-background-tasks-observability\u002Fcaching-strategies","async-background-tasks-observability\u002Fcaching-strategies\u002Findex",[289,290,296,302],{"title":285,"path":286,"stem":287},{"title":291,"path":292,"stem":293,"children":294},"Cache Invalidation Patterns in FastAPI","\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fcache-invalidation-patterns-in-fastapi","async-background-tasks-observability\u002Fcaching-strategies\u002Fcache-invalidation-patterns-in-fastapi\u002Findex",[295],{"title":291,"path":292,"stem":293},{"title":297,"path":298,"stem":299,"children":300},"Caching Dependency Results in FastAPI","\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fcaching-dependency-results","async-background-tasks-observability\u002Fcaching-strategies\u002Fcaching-dependency-results\u002Findex",[301],{"title":297,"path":298,"stem":299},{"title":303,"path":304,"stem":305,"children":306},"Redis Response Caching in FastAPI","\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fredis-response-caching-in-fastapi","async-background-tasks-observability\u002Fcaching-strategies\u002Fredis-response-caching-in-fastapi\u002Findex",[307],{"title":303,"path":304,"stem":305},{"title":309,"path":310,"stem":311,"children":312},"Observability and Tracing in FastAPI","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing","async-background-tasks-observability\u002Fobservability-and-tracing\u002Findex",[313,314,320,326,332],{"title":309,"path":310,"stem":311},{"title":315,"path":316,"stem":317,"children":318},"Correlating Logs, Traces and Errors in FastAPI","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Fcorrelating-logs-traces-and-errors","async-background-tasks-observability\u002Fobservability-and-tracing\u002Fcorrelating-logs-traces-and-errors\u002Findex",[319],{"title":315,"path":316,"stem":317},{"title":321,"path":322,"stem":323,"children":324},"Instrumenting FastAPI with OpenTelemetry","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Finstrumenting-fastapi-with-opentelemetry","async-background-tasks-observability\u002Fobservability-and-tracing\u002Finstrumenting-fastapi-with-opentelemetry\u002Findex",[325],{"title":321,"path":322,"stem":323},{"title":327,"path":328,"stem":329,"children":330},"Prometheus Metrics for FastAPI","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Fprometheus-metrics-for-fastapi","async-background-tasks-observability\u002Fobservability-and-tracing\u002Fprometheus-metrics-for-fastapi\u002Findex",[331],{"title":327,"path":328,"stem":329},{"title":333,"path":334,"stem":335,"children":336},"Structured JSON Logging with Request IDs in FastAPI","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Fstructured-json-logging-with-request-ids","async-background-tasks-observability\u002Fobservability-and-tracing\u002Fstructured-json-logging-with-request-ids\u002Findex",[337],{"title":333,"path":334,"stem":335},{"title":339,"path":340,"stem":341,"children":342},"Rate Limiting and Throttling in FastAPI","\u002Fasync-background-tasks-observability\u002Frate-limiting-throttling","async-background-tasks-observability\u002Frate-limiting-throttling\u002Findex",[343,344,350,356],{"title":339,"path":340,"stem":341},{"title":345,"path":346,"stem":347,"children":348},"FastAPI Rate Limiting with Redis and SlowAPI","\u002Fasync-background-tasks-observability\u002Frate-limiting-throttling\u002Ffastapi-rate-limiting-with-redis-slowapi","async-background-tasks-observability\u002Frate-limiting-throttling\u002Ffastapi-rate-limiting-with-redis-slowapi\u002Findex",[349],{"title":345,"path":346,"stem":347},{"title":351,"path":352,"stem":353,"children":354},"Per-User Token Bucket Throttling in FastAPI","\u002Fasync-background-tasks-observability\u002Frate-limiting-throttling\u002Fper-user-token-bucket-throttling","async-background-tasks-observability\u002Frate-limiting-throttling\u002Fper-user-token-bucket-throttling\u002Findex",[355],{"title":351,"path":352,"stem":353},{"title":357,"path":358,"stem":359,"children":360},"Rate Limit Headers and 429 Responses in FastAPI","\u002Fasync-background-tasks-observability\u002Frate-limiting-throttling\u002Frate-limit-headers-and-429-responses","async-background-tasks-observability\u002Frate-limiting-throttling\u002Frate-limit-headers-and-429-responses\u002Findex",[361],{"title":357,"path":358,"stem":359},{"title":363,"path":364,"stem":365,"children":366},"Testing FastAPI Applications","\u002Fasync-background-tasks-observability\u002Ftesting-fastapi-applications","async-background-tasks-observability\u002Ftesting-fastapi-applications\u002Findex",[367,368,374,380],{"title":363,"path":364,"stem":365},{"title":369,"path":370,"stem":371,"children":372},"Mocking External Services in FastAPI Tests","\u002Fasync-background-tasks-observability\u002Ftesting-fastapi-applications\u002Fmocking-external-services-in-tests","async-background-tasks-observability\u002Ftesting-fastapi-applications\u002Fmocking-external-services-in-tests\u002Findex",[373],{"title":369,"path":370,"stem":371},{"title":375,"path":376,"stem":377,"children":378},"TestClient vs httpx AsyncClient in FastAPI","\u002Fasync-background-tasks-observability\u002Ftesting-fastapi-applications\u002Ftestclient-vs-httpx-asyncclient","async-background-tasks-observability\u002Ftesting-fastapi-applications\u002Ftestclient-vs-httpx-asyncclient\u002Findex",[379],{"title":375,"path":376,"stem":377},{"title":381,"path":382,"stem":383,"children":384},"Testing Async FastAPI Endpoints with pytest-asyncio","\u002Fasync-background-tasks-observability\u002Ftesting-fastapi-applications\u002Ftesting-async-endpoints-with-pytest-asyncio","async-background-tasks-observability\u002Ftesting-fastapi-applications\u002Ftesting-async-endpoints-with-pytest-asyncio\u002Findex",[385],{"title":381,"path":382,"stem":383},{"title":387,"path":388,"stem":389,"children":390},"Core Architecture Routing Patterns","\u002Fcore-architecture-routing-patterns","core-architecture-routing-patterns",[391,394,412,436,472,496,526,556],{"title":392,"path":388,"stem":393},"FastAPI Core Architecture and Routing Patterns","core-architecture-routing-patterns\u002Findex",{"title":395,"path":396,"stem":397,"children":398},"Application Factory Patterns in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fapplication-factory-patterns","core-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Findex",[399,400,406],{"title":395,"path":396,"stem":397},{"title":401,"path":402,"stem":403,"children":404},"FastAPI App Factory Pattern for Testing and Deployment","\u002Fcore-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Ffastapi-app-factory-pattern-for-testing-and-deployment","core-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Ffastapi-app-factory-pattern-for-testing-and-deployment\u002Findex",[405],{"title":401,"path":402,"stem":403},{"title":407,"path":408,"stem":409,"children":410},"Lifespan Events vs Startup and Shutdown in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Flifespan-events-vs-startup-shutdown","core-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Flifespan-events-vs-startup-shutdown\u002Findex",[411],{"title":407,"path":408,"stem":409},{"title":413,"path":414,"stem":415,"children":416},"Configuration Management in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fconfiguration-management","core-architecture-routing-patterns\u002Fconfiguration-management\u002Findex",[417,418,424,430],{"title":413,"path":414,"stem":415},{"title":419,"path":420,"stem":421,"children":422},"Managing Environment Variables with Pydantic Settings","\u002Fcore-architecture-routing-patterns\u002Fconfiguration-management\u002Fmanaging-environment-variables-with-pydantic-settings","core-architecture-routing-patterns\u002Fconfiguration-management\u002Fmanaging-environment-variables-with-pydantic-settings\u002Findex",[423],{"title":419,"path":420,"stem":421},{"title":425,"path":426,"stem":427,"children":428},"Pydantic Settings vs Dynaconf vs python-decouple","\u002Fcore-architecture-routing-patterns\u002Fconfiguration-management\u002Fpydantic-settings-vs-dynaconf-vs-python-decouple","core-architecture-routing-patterns\u002Fconfiguration-management\u002Fpydantic-settings-vs-dynaconf-vs-python-decouple\u002Findex",[429],{"title":425,"path":426,"stem":427},{"title":431,"path":432,"stem":433,"children":434},"Secrets and .env Files Per Environment in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fconfiguration-management\u002Fsecrets-and-env-files-per-environment","core-architecture-routing-patterns\u002Fconfiguration-management\u002Fsecrets-and-env-files-per-environment\u002Findex",[435],{"title":431,"path":432,"stem":433},{"title":437,"path":438,"stem":439,"children":440},"Dependency Injection Strategies in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Findex",[441,442,448,454,460,466],{"title":437,"path":438,"stem":439},{"title":443,"path":444,"stem":445,"children":446},"Best Practices for FastAPI Dependency Injection","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fbest-practices-for-fastapi-dependency-injection","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fbest-practices-for-fastapi-dependency-injection\u002Findex",[447],{"title":443,"path":444,"stem":445},{"title":449,"path":450,"stem":451,"children":452},"Dependency Caching and use_cache in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fdependency-caching-and-use-cache","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fdependency-caching-and-use-cache\u002Findex",[453],{"title":449,"path":450,"stem":451},{"title":455,"path":456,"stem":457,"children":458},"Fixing FastAPI Dependency Injection Circular Imports","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Ffastapi-dependency-injection-circular-import-fix","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Ffastapi-dependency-injection-circular-import-fix\u002Findex",[459],{"title":455,"path":456,"stem":457},{"title":461,"path":462,"stem":463,"children":464},"Overriding Dependencies in FastAPI Tests","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Foverriding-dependencies-in-tests","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Foverriding-dependencies-in-tests\u002Findex",[465],{"title":461,"path":462,"stem":463},{"title":467,"path":468,"stem":469,"children":470},"Yield Dependencies and Cleanup Order in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fyield-dependencies-and-cleanup-order","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fyield-dependencies-and-cleanup-order\u002Findex",[471],{"title":467,"path":468,"stem":469},{"title":473,"path":474,"stem":475,"children":476},"Error Handling and Global Exceptions in FastAPI","\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions","core-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Findex",[477,478,484,490],{"title":473,"path":474,"stem":475},{"title":479,"path":480,"stem":481,"children":482},"Customising Validation Error Responses in FastAPI","\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fcustomising-validation-error-responses","core-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fcustomising-validation-error-responses\u002Findex",[483],{"title":479,"path":480,"stem":481},{"title":485,"path":486,"stem":487,"children":488},"Global Exception Handlers for Consistent API Responses","\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fglobal-exception-handlers-for-consistent-api-responses","core-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fglobal-exception-handlers-for-consistent-api-responses\u002Findex",[489],{"title":485,"path":486,"stem":487},{"title":491,"path":492,"stem":493,"children":494},"HTTPException vs Custom Exception Classes in FastAPI","\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fhttpexception-vs-custom-exception-classes","core-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fhttpexception-vs-custom-exception-classes\u002Findex",[495],{"title":491,"path":492,"stem":493},{"title":497,"path":498,"stem":499,"children":500},"Middleware Implementation in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Findex",[501,502,508,514,520],{"title":497,"path":498,"stem":499},{"title":503,"path":504,"stem":505,"children":506},"CORS Middleware Configuration in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fcors-middleware-configuration","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fcors-middleware-configuration\u002Findex",[507],{"title":503,"path":504,"stem":505},{"title":509,"path":510,"stem":511,"children":512},"Implementing Custom Middleware for Request Tracing","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fimplementing-custom-middleware-for-request-tracing","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fimplementing-custom-middleware-for-request-tracing\u002Findex",[513],{"title":509,"path":510,"stem":511},{"title":515,"path":516,"stem":517,"children":518},"Middleware Execution Order in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fmiddleware-execution-order","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fmiddleware-execution-order\u002Findex",[519],{"title":515,"path":516,"stem":517},{"title":521,"path":522,"stem":523,"children":524},"Middleware vs Dependencies: When to Use Which","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fmiddleware-vs-dependencies-when-to-use-which","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fmiddleware-vs-dependencies-when-to-use-which\u002Findex",[525],{"title":521,"path":522,"stem":523},{"title":527,"path":528,"stem":529,"children":530},"Modular Router Organization in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Findex",[531,532,538,544,550],{"title":527,"path":528,"stem":529},{"title":533,"path":534,"stem":535,"children":536},"APIRouter Prefix vs Sub-Application Mounting in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Fapirouter-prefix-vs-sub-application-mounting","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Fapirouter-prefix-vs-sub-application-mounting\u002Findex",[537],{"title":533,"path":534,"stem":535},{"title":539,"path":540,"stem":541,"children":542},"How to Structure Large FastAPI Projects for Scale","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Fhow-to-structure-large-fastapi-projects-for-scale","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Fhow-to-structure-large-fastapi-projects-for-scale\u002Findex",[543],{"title":539,"path":540,"stem":541},{"title":545,"path":546,"stem":547,"children":548},"Router Tags and OpenAPI Grouping in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Frouter-tags-and-openapi-grouping","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Frouter-tags-and-openapi-grouping\u002Findex",[549],{"title":545,"path":546,"stem":547},{"title":551,"path":552,"stem":553,"children":554},"Versioning APIs with FastAPI Routers","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Fversioning-apis-with-routers","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Fversioning-apis-with-routers\u002Findex",[555],{"title":551,"path":552,"stem":553},{"title":557,"path":558,"stem":559,"children":560},"The FastAPI Request\u002FResponse Lifecycle","\u002Fcore-architecture-routing-patterns\u002Frequest-response-lifecycle","core-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Findex",[561,562,568,574],{"title":557,"path":558,"stem":559},{"title":563,"path":564,"stem":565,"children":566},"How a Request Flows Through FastAPI","\u002Fcore-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fhow-a-request-flows-through-fastapi","core-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fhow-a-request-flows-through-fastapi\u002Findex",[567],{"title":563,"path":564,"stem":565},{"title":569,"path":570,"stem":571,"children":572},"Response Model and Serialization Order","\u002Fcore-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fresponse-model-and-serialization-order","core-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fresponse-model-and-serialization-order\u002Findex",[573],{"title":569,"path":570,"stem":571},{"title":575,"path":576,"stem":577,"children":578},"Streaming and File Responses in FastAPI","\u002Fcore-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fstreaming-and-file-responses","core-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fstreaming-and-file-responses\u002Findex",[579],{"title":575,"path":576,"stem":577},{"id":581,"title":139,"body":582,"dateModified":2409,"datePublished":2409,"description":2410,"extension":2411,"faq":2412,"howto":2423,"meta":2424,"navigation":718,"path":140,"seo":2437,"stem":141,"type":2438,"__hash__":2439},"content\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Freplacing-json-encoders-with-field-serializer\u002Findex.md",{"type":583,"value":584,"toc":2390},"minimark",[585,589,596,645,654,659,662,787,812,824,977,981,992,996,1001,1010,1018,1026,1037,1044,1051,1061,1064,1724,1738,1745,1752,1755,1795,1821,1838,1842,1910,1941,1945,2029,2033,2036,2210,2216,2220,2236,2246,2250,2263,2281,2293,2316,2341,2345,2386],[586,587,139],"h1",{"id":588},"replacing-json_encoders-with-field_serializer-in-pydantic-v2",[590,591,592],"p",{},[593,594,595],"strong",{},"Key takeaways:",[597,598,599,607,621,627,638],"ul",{},[600,601,602,606],"li",{},[603,604,605],"code",{},"Config.json_encoders"," is gone in Pydantic v2; there is no drop-in replacement dict.",[600,608,609,612,613,616,617,620],{},[603,610,611],{},"@field_serializer(\"name\")"," formats one field and applies to ",[603,614,615],{},"model_dump()"," and ",[603,618,619],{},"model_dump_json()"," alike.",[600,622,623,626],{},[603,624,625],{},"@model_serializer"," replaces the whole model's output when the wire shape differs from the model shape.",[600,628,629,632,633,637],{},[603,630,631],{},"Annotated[T, PlainSerializer(fn)]"," attaches the rule to the ",[634,635,636],"em",{},"type",", so it is reusable across models.",[600,639,640,641,644],{},"Serializers changed the ",[603,642,643],{},"python"," dump mode too — v1 encoders only ran on the JSON path.",[590,646,647,648,653],{},"This page is a focused slice of the ",[649,650,652],"a",{"href":651},"\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002F","Pydantic V2 Migration Guide",", covering the one config key that has no mechanical equivalent.",[655,656,658],"h2",{"id":657},"the-problem-this-solves","The Problem This Solves",[590,660,661],{},"In Pydantic v1 you taught a model how to render awkward types with a dict in the model config:",[663,664,668],"pre",{"className":665,"code":666,"language":643,"meta":667,"style":667},"language-python shiki shiki-themes github-light-high-contrast","# Pydantic v1 — this is the idiom being removed. It is NOT executable in v2.\nclass Invoice(BaseModel):\n    issued_at: datetime\n    total: Decimal\n\n    class Config:\n        json_encoders = {\n            datetime: lambda v: v.strftime(\"%Y-%m-%dT%H:%M:%SZ\"),\n            Decimal: float,\n        }\n","",[603,669,670,679,701,707,713,720,732,744,769,781],{"__ignoreMap":667},[671,672,675],"span",{"class":673,"line":674},"line",1,[671,676,678],{"class":677},"sFeEa","# Pydantic v1 — this is the idiom being removed. It is NOT executable in v2.\n",[671,680,682,686,690,694,698],{"class":673,"line":681},2,[671,683,685],{"class":684},"sTJeM","class",[671,687,689],{"class":688},"sV4o_"," Invoice",[671,691,693],{"class":692},"sigWx","(",[671,695,697],{"class":696},"sacAq","BaseModel",[671,699,700],{"class":692},"):\n",[671,702,704],{"class":673,"line":703},3,[671,705,706],{"class":692},"    issued_at: datetime\n",[671,708,710],{"class":673,"line":709},4,[671,711,712],{"class":692},"    total: Decimal\n",[671,714,716],{"class":673,"line":715},5,[671,717,719],{"emptyLinePlaceholder":718},true,"\n",[671,721,723,726,729],{"class":673,"line":722},6,[671,724,725],{"class":684},"    class",[671,727,728],{"class":688}," Config",[671,730,731],{"class":692},":\n",[671,733,735,738,741],{"class":673,"line":734},7,[671,736,737],{"class":692},"        json_encoders ",[671,739,740],{"class":684},"=",[671,742,743],{"class":692}," {\n",[671,745,747,750,753,756,760,763,766],{"class":673,"line":746},8,[671,748,749],{"class":692},"            datetime: ",[671,751,752],{"class":684},"lambda",[671,754,755],{"class":692}," v: v.strftime(",[671,757,759],{"class":758},"sYEJz","\"%Y-%m-",[671,761,762],{"class":684},"%d",[671,764,765],{"class":758},"T%H:%M:%SZ\"",[671,767,768],{"class":692},"),\n",[671,770,772,775,778],{"class":673,"line":771},9,[671,773,774],{"class":692},"            Decimal: ",[671,776,777],{"class":696},"float",[671,779,780],{"class":692},",\n",[671,782,784],{"class":673,"line":783},10,[671,785,786],{"class":692},"        }\n",[590,788,789,790,793,794,797,798,616,801,804,805,807,808,811],{},"It was convenient and it was quietly wrong in three ways. It ran only when you called ",[603,791,792],{},".json()",", so ",[603,795,796],{},".dict()"," returned the raw ",[603,799,800],{},"datetime",[603,802,803],{},"Decimal"," objects and the two paths disagreed. It was keyed by type, so a model with two ",[603,806,800],{}," fields could not format them differently. And the schema generator had no idea it existed, so OpenAPI advertised ",[603,809,810],{},"format: date-time"," for a field that actually shipped a custom string.",[590,813,814,815,818,819,823],{},"Pydantic v2 pushed serialization into the same Rust core that does validation. A serializer is now a first-class part of the field's schema rather than a Python callback bolted on at the end, which is why ",[603,816,817],{},"json_encoders"," had nowhere to live. Unlike the ",[649,820,822],{"href":821},"\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmodel-config-vs-class-config\u002F","config key renames",", there is no one-to-one substitution here — you have to decide, per field, which of three replacements fits.",[825,826,827,973],"figure",{},[828,829,837,841,845,852,857,866,871,877,882,885,888,891,895,900,904,908,912,916,919,923,927,932,936,940,943,946,949,952,955,958,961,964,968,970],"svg",{"viewBox":830,"role":831,"ariaLabelledBy":832,"xmlns":835,"style":836},"0 0 720 300","img",[833,834],"ser-title","ser-desc","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;height:auto;max-width:720px;margin:2rem 0",[838,839,840],"title",{"id":833},"Where v1 json_encoders ran versus where v2 serializers run",[842,843,844],"desc",{"id":834},"In v1 a single json_encoders dict was applied only on the JSON output path, so dict output bypassed it. In v2 the serializer is attached to the field inside the core and applies to both python and JSON dump modes.",[846,847,851],"text",{"x":848,"y":849,"style":850},"180","26","text-anchor:middle;fill:currentColor;font:600 14px sans-serif","Pydantic v1",[846,853,856],{"x":854,"y":849,"style":855},"530","text-anchor:middle;fill:#00796B;font:600 14px sans-serif","Pydantic v2",[858,859],"rect",{"x":860,"y":861,"width":862,"height":863,"rx":864,"style":865},"52","44","256","40","8","fill:none;stroke:currentColor;stroke-width:1.4",[846,867,870],{"x":848,"y":868,"style":869},"69","text-anchor:middle;fill:currentColor;font:500 13px sans-serif","model instance",[673,872],{"x1":873,"y1":874,"x2":873,"y2":875,"style":876},"120","84","112","stroke:currentColor;stroke-width:1.4",[878,879],"polygon",{"points":880,"style":881},"116,112 124,112 120,120","fill:currentColor",[673,883],{"x1":884,"y1":874,"x2":884,"y2":875,"style":876},"240",[878,886],{"points":887,"style":881},"236,112 244,112 240,120",[858,889],{"x":860,"y":890,"width":873,"height":861,"rx":864,"style":865},"122",[846,892,796],{"x":875,"y":893,"style":894},"141","text-anchor:middle;fill:currentColor;font:500 12px sans-serif",[846,896,899],{"x":875,"y":897,"style":898},"157","text-anchor:middle;fill:currentColor;font:400 11px sans-serif","raw types",[858,901],{"x":902,"y":890,"width":873,"height":861,"rx":864,"style":903},"188","fill:none;stroke:#00796B;stroke-width:1.6",[846,905,792],{"x":906,"y":893,"style":907},"248","text-anchor:middle;fill:#00796B;font:500 12px sans-serif",[846,909,911],{"x":906,"y":897,"style":910},"text-anchor:middle;fill:#00796B;font:400 11px sans-serif","encoders run",[858,913],{"x":902,"y":914,"width":873,"height":915,"rx":864,"style":903},"186","38",[846,917,817],{"x":906,"y":918,"style":907},"210",[673,920],{"x1":906,"y1":921,"x2":906,"y2":848,"style":922},"166","stroke:#00796B;stroke-width:1.4",[878,924],{"points":925,"style":926},"244,180 252,180 248,186","fill:#00796B",[846,928,931],{"x":848,"y":929,"style":930},"252","text-anchor:middle;fill:currentColor;font:400 11.5px sans-serif","two paths, two answers",[858,933],{"x":934,"y":861,"width":935,"height":863,"rx":864,"style":903},"400","260",[846,937,939],{"x":854,"y":868,"style":938},"text-anchor:middle;fill:#00796B;font:500 13px sans-serif","field serializer in core",[673,941],{"x1":942,"y1":874,"x2":942,"y2":875,"style":922},"470",[878,944],{"points":945,"style":926},"466,112 474,112 470,120",[673,947],{"x1":948,"y1":874,"x2":948,"y2":875,"style":922},"590",[878,950],{"points":951,"style":926},"586,112 594,112 590,120",[858,953],{"x":934,"y":890,"width":954,"height":861,"rx":864,"style":903},"124",[846,956,615],{"x":957,"y":893,"style":907},"462",[846,959,960],{"x":957,"y":897,"style":910},"serialized",[858,962],{"x":963,"y":890,"width":954,"height":861,"rx":864,"style":903},"536",[846,965,967],{"x":966,"y":893,"style":907},"598","dump_json()",[846,969,960],{"x":966,"y":897,"style":910},[846,971,972],{"x":854,"y":929,"style":930},"one rule, both paths agree",[974,975,976],"figcaption",{},"v1 encoders were a JSON-only post-processing step; a v2 serializer lives on the field inside the core and applies to every dump mode.",[655,978,980],{"id":979},"prerequisites","Prerequisites",[597,982,983,986],{},[600,984,985],{},"Pydantic 2.13.4, FastAPI 0.139.2, Python 3.12 — the versions every transcript below was produced on.",[600,987,988,989,991],{},"A model that currently carries ",[603,990,817],{},", or a custom type FastAPI cannot encode.",[655,993,995],{"id":994},"step-by-step-implementation","Step-by-Step Implementation",[997,998,1000],"h3",{"id":999},"_1-establish-the-baseline-what-happens-with-no-serializer","1. Establish the baseline: what happens with no serializer",[590,1002,1003,1004,1006,1007,1009],{},"Before replacing anything, look at what the core does on its own. A ",[603,1005,800],{}," and a ",[603,1008,803],{}," both have built-in serializers, and they are probably not what your v1 encoders were producing.",[997,1011,1013,1014,1017],{"id":1012},"_2-attach-field_serializer-per-field","2. Attach ",[603,1015,1016],{},"@field_serializer"," per field",[590,1019,1020,1022,1023,1025],{},[603,1021,1016],{}," names the field or fields it applies to and receives the already-validated value. It is a normal method, so a model with two ",[603,1024,800],{}," fields can format each one differently — the thing v1's type-keyed dict could not do.",[997,1027,1029,1030,1033,1034],{"id":1028},"_3-attach-plainserializer-to-the-type-with-annotated","3. Attach ",[603,1031,1032],{},"PlainSerializer"," to the type with ",[603,1035,1036],{},"Annotated",[590,1038,1039,1040,1043],{},"When the rule belongs to a type rather than to a model — a domain ",[603,1041,1042],{},"Money"," object, an internal ID wrapper — put it on the type. Every field annotated with it inherits the behaviour, and you do not repeat a decorator in ten models.",[997,1045,1047,1048,1050],{"id":1046},"_4-use-model_serializer-when-the-shape-changes","4. Use ",[603,1049,625],{}," when the shape changes",[590,1052,1053,1054,1057,1058,1060],{},"If the response envelope differs structurally from the model — a ",[603,1055,1056],{},"{\"type\": ..., \"data\": {...}}"," wrapper, a flattened representation — ",[603,1059,625],{}," takes over the whole output and you build the dict yourself.",[590,1062,1063],{},"Here is the complete example, exercised end to end:",[663,1065,1067],{"className":665,"code":1066,"language":643,"meta":667,"style":667},"\"\"\"Replacing v1 Config.json_encoders with field_serializer, model_serializer and PlainSerializer.\"\"\"\nfrom datetime import datetime, timezone\nfrom decimal import Decimal\nfrom typing import Annotated, Any\n\nfrom fastapi import FastAPI\nfrom pydantic import BaseModel, PlainSerializer, field_serializer, model_serializer\n\napp = FastAPI()\n\n\nclass Money:\n    \"\"\"A custom type Pydantic knows nothing about until we tell it how to serialize.\"\"\"\n\n    def __init__(self, amount: Decimal, currency: str) -> None:\n        self.amount = amount\n        self.currency = currency\n\n\ndef money_to_str(value: Money) -> str:\n    return f\"{value.amount:.2f} {value.currency}\"\n\n\n# PlainSerializer attaches the rule to the *type*, so every field using it inherits the rule.\nSerializedMoney = Annotated[\n    Money,\n    PlainSerializer(money_to_str, return_type=str, when_used=\"always\"),\n]\n\n\nclass Invoice(BaseModel):\n    model_config = {\"arbitrary_types_allowed\": True}\n\n    id: int\n    issued_at: datetime\n    total: Decimal\n    balance: SerializedMoney\n\n    # Replaces v1's Config.json_encoders = {datetime: lambda v: v.isoformat()}\n    @field_serializer(\"issued_at\")\n    def serialize_issued_at(self, value: datetime) -> str:\n        return value.strftime(\"%Y-%m-%dT%H:%M:%SZ\")\n\n    # Replaces v1's Config.json_encoders = {Decimal: float}\n    @field_serializer(\"total\")\n    def serialize_total(self, value: Decimal) -> float:\n        return float(value)\n\n\nclass Receipt(BaseModel):\n    \"\"\"model_serializer takes over the whole model's output shape.\"\"\"\n\n    id: int\n    issued_at: datetime\n    total: Decimal\n\n    @model_serializer\n    def to_envelope(self) -> dict[str, Any]:\n        return {\n            \"type\": \"receipt\",\n            \"data\": {\n                \"id\": self.id,\n                \"issued_at\": self.issued_at.strftime(\"%Y-%m-%dT%H:%M:%SZ\"),\n                \"total\": f\"{self.total:.2f}\",\n            },\n        }\n",[603,1068,1069,1074,1088,1100,1112,1116,1128,1140,1144,1154,1158,1163,1173,1179,1184,1207,1221,1234,1239,1244,1261,1294,1299,1304,1310,1321,1327,1353,1359,1364,1369,1382,1404,1409,1420,1425,1430,1436,1441,1447,1461,1476,1493,1498,1504,1516,1531,1542,1547,1552,1566,1572,1577,1586,1591,1596,1601,1607,1623,1630,1643,1652,1666,1687,1713,1719],{"__ignoreMap":667},[671,1070,1071],{"class":673,"line":674},[671,1072,1073],{"class":758},"\"\"\"Replacing v1 Config.json_encoders with field_serializer, model_serializer and PlainSerializer.\"\"\"\n",[671,1075,1076,1079,1082,1085],{"class":673,"line":681},[671,1077,1078],{"class":684},"from",[671,1080,1081],{"class":692}," datetime ",[671,1083,1084],{"class":684},"import",[671,1086,1087],{"class":692}," datetime, timezone\n",[671,1089,1090,1092,1095,1097],{"class":673,"line":703},[671,1091,1078],{"class":684},[671,1093,1094],{"class":692}," decimal ",[671,1096,1084],{"class":684},[671,1098,1099],{"class":692}," Decimal\n",[671,1101,1102,1104,1107,1109],{"class":673,"line":709},[671,1103,1078],{"class":684},[671,1105,1106],{"class":692}," typing ",[671,1108,1084],{"class":684},[671,1110,1111],{"class":692}," Annotated, Any\n",[671,1113,1114],{"class":673,"line":715},[671,1115,719],{"emptyLinePlaceholder":718},[671,1117,1118,1120,1123,1125],{"class":673,"line":722},[671,1119,1078],{"class":684},[671,1121,1122],{"class":692}," fastapi ",[671,1124,1084],{"class":684},[671,1126,1127],{"class":692}," FastAPI\n",[671,1129,1130,1132,1135,1137],{"class":673,"line":734},[671,1131,1078],{"class":684},[671,1133,1134],{"class":692}," pydantic ",[671,1136,1084],{"class":684},[671,1138,1139],{"class":692}," BaseModel, PlainSerializer, field_serializer, model_serializer\n",[671,1141,1142],{"class":673,"line":746},[671,1143,719],{"emptyLinePlaceholder":718},[671,1145,1146,1149,1151],{"class":673,"line":771},[671,1147,1148],{"class":692},"app ",[671,1150,740],{"class":684},[671,1152,1153],{"class":692}," FastAPI()\n",[671,1155,1156],{"class":673,"line":783},[671,1157,719],{"emptyLinePlaceholder":718},[671,1159,1161],{"class":673,"line":1160},11,[671,1162,719],{"emptyLinePlaceholder":718},[671,1164,1166,1168,1171],{"class":673,"line":1165},12,[671,1167,685],{"class":684},[671,1169,1170],{"class":688}," Money",[671,1172,731],{"class":692},[671,1174,1176],{"class":673,"line":1175},13,[671,1177,1178],{"class":758},"    \"\"\"A custom type Pydantic knows nothing about until we tell it how to serialize.\"\"\"\n",[671,1180,1182],{"class":673,"line":1181},14,[671,1183,719],{"emptyLinePlaceholder":718},[671,1185,1187,1190,1193,1196,1199,1202,1205],{"class":673,"line":1186},15,[671,1188,1189],{"class":684},"    def",[671,1191,1192],{"class":696}," __init__",[671,1194,1195],{"class":692},"(self, amount: Decimal, currency: ",[671,1197,1198],{"class":696},"str",[671,1200,1201],{"class":692},") -> ",[671,1203,1204],{"class":696},"None",[671,1206,731],{"class":692},[671,1208,1210,1213,1216,1218],{"class":673,"line":1209},16,[671,1211,1212],{"class":696},"        self",[671,1214,1215],{"class":692},".amount ",[671,1217,740],{"class":684},[671,1219,1220],{"class":692}," amount\n",[671,1222,1224,1226,1229,1231],{"class":673,"line":1223},17,[671,1225,1212],{"class":696},[671,1227,1228],{"class":692},".currency ",[671,1230,740],{"class":684},[671,1232,1233],{"class":692}," currency\n",[671,1235,1237],{"class":673,"line":1236},18,[671,1238,719],{"emptyLinePlaceholder":718},[671,1240,1242],{"class":673,"line":1241},19,[671,1243,719],{"emptyLinePlaceholder":718},[671,1245,1247,1250,1254,1257,1259],{"class":673,"line":1246},20,[671,1248,1249],{"class":684},"def",[671,1251,1253],{"class":1252},"s3dhs"," money_to_str",[671,1255,1256],{"class":692},"(value: Money) -> ",[671,1258,1198],{"class":696},[671,1260,731],{"class":692},[671,1262,1264,1267,1270,1273,1276,1279,1282,1285,1288,1291],{"class":673,"line":1263},21,[671,1265,1266],{"class":684},"    return",[671,1268,1269],{"class":684}," f",[671,1271,1272],{"class":758},"\"",[671,1274,1275],{"class":684},"{",[671,1277,1278],{"class":692},"value.amount",[671,1280,1281],{"class":684},":.2f}",[671,1283,1284],{"class":684}," {",[671,1286,1287],{"class":692},"value.currency",[671,1289,1290],{"class":684},"}",[671,1292,1293],{"class":758},"\"\n",[671,1295,1297],{"class":673,"line":1296},22,[671,1298,719],{"emptyLinePlaceholder":718},[671,1300,1302],{"class":673,"line":1301},23,[671,1303,719],{"emptyLinePlaceholder":718},[671,1305,1307],{"class":673,"line":1306},24,[671,1308,1309],{"class":677},"# PlainSerializer attaches the rule to the *type*, so every field using it inherits the rule.\n",[671,1311,1313,1316,1318],{"class":673,"line":1312},25,[671,1314,1315],{"class":692},"SerializedMoney ",[671,1317,740],{"class":684},[671,1319,1320],{"class":692}," Annotated[\n",[671,1322,1324],{"class":673,"line":1323},26,[671,1325,1326],{"class":692},"    Money,\n",[671,1328,1330,1333,1336,1338,1340,1343,1346,1348,1351],{"class":673,"line":1329},27,[671,1331,1332],{"class":692},"    PlainSerializer(money_to_str, ",[671,1334,1335],{"class":688},"return_type",[671,1337,740],{"class":684},[671,1339,1198],{"class":696},[671,1341,1342],{"class":692},", ",[671,1344,1345],{"class":688},"when_used",[671,1347,740],{"class":684},[671,1349,1350],{"class":758},"\"always\"",[671,1352,768],{"class":692},[671,1354,1356],{"class":673,"line":1355},28,[671,1357,1358],{"class":692},"]\n",[671,1360,1362],{"class":673,"line":1361},29,[671,1363,719],{"emptyLinePlaceholder":718},[671,1365,1367],{"class":673,"line":1366},30,[671,1368,719],{"emptyLinePlaceholder":718},[671,1370,1372,1374,1376,1378,1380],{"class":673,"line":1371},31,[671,1373,685],{"class":684},[671,1375,689],{"class":688},[671,1377,693],{"class":692},[671,1379,697],{"class":696},[671,1381,700],{"class":692},[671,1383,1385,1388,1390,1392,1395,1398,1401],{"class":673,"line":1384},32,[671,1386,1387],{"class":692},"    model_config ",[671,1389,740],{"class":684},[671,1391,1284],{"class":692},[671,1393,1394],{"class":758},"\"arbitrary_types_allowed\"",[671,1396,1397],{"class":692},": ",[671,1399,1400],{"class":696},"True",[671,1402,1403],{"class":692},"}\n",[671,1405,1407],{"class":673,"line":1406},33,[671,1408,719],{"emptyLinePlaceholder":718},[671,1410,1412,1415,1417],{"class":673,"line":1411},34,[671,1413,1414],{"class":696},"    id",[671,1416,1397],{"class":692},[671,1418,1419],{"class":696},"int\n",[671,1421,1423],{"class":673,"line":1422},35,[671,1424,706],{"class":692},[671,1426,1428],{"class":673,"line":1427},36,[671,1429,712],{"class":692},[671,1431,1433],{"class":673,"line":1432},37,[671,1434,1435],{"class":692},"    balance: SerializedMoney\n",[671,1437,1439],{"class":673,"line":1438},38,[671,1440,719],{"emptyLinePlaceholder":718},[671,1442,1444],{"class":673,"line":1443},39,[671,1445,1446],{"class":677},"    # Replaces v1's Config.json_encoders = {datetime: lambda v: v.isoformat()}\n",[671,1448,1450,1453,1455,1458],{"class":673,"line":1449},40,[671,1451,1452],{"class":1252},"    @field_serializer",[671,1454,693],{"class":692},[671,1456,1457],{"class":758},"\"issued_at\"",[671,1459,1460],{"class":692},")\n",[671,1462,1464,1466,1469,1472,1474],{"class":673,"line":1463},41,[671,1465,1189],{"class":684},[671,1467,1468],{"class":1252}," serialize_issued_at",[671,1470,1471],{"class":692},"(self, value: datetime) -> ",[671,1473,1198],{"class":696},[671,1475,731],{"class":692},[671,1477,1479,1482,1485,1487,1489,1491],{"class":673,"line":1478},42,[671,1480,1481],{"class":684},"        return",[671,1483,1484],{"class":692}," value.strftime(",[671,1486,759],{"class":758},[671,1488,762],{"class":684},[671,1490,765],{"class":758},[671,1492,1460],{"class":692},[671,1494,1496],{"class":673,"line":1495},43,[671,1497,719],{"emptyLinePlaceholder":718},[671,1499,1501],{"class":673,"line":1500},44,[671,1502,1503],{"class":677},"    # Replaces v1's Config.json_encoders = {Decimal: float}\n",[671,1505,1507,1509,1511,1514],{"class":673,"line":1506},45,[671,1508,1452],{"class":1252},[671,1510,693],{"class":692},[671,1512,1513],{"class":758},"\"total\"",[671,1515,1460],{"class":692},[671,1517,1519,1521,1524,1527,1529],{"class":673,"line":1518},46,[671,1520,1189],{"class":684},[671,1522,1523],{"class":1252}," serialize_total",[671,1525,1526],{"class":692},"(self, value: Decimal) -> ",[671,1528,777],{"class":696},[671,1530,731],{"class":692},[671,1532,1534,1536,1539],{"class":673,"line":1533},47,[671,1535,1481],{"class":684},[671,1537,1538],{"class":696}," float",[671,1540,1541],{"class":692},"(value)\n",[671,1543,1545],{"class":673,"line":1544},48,[671,1546,719],{"emptyLinePlaceholder":718},[671,1548,1550],{"class":673,"line":1549},49,[671,1551,719],{"emptyLinePlaceholder":718},[671,1553,1555,1557,1560,1562,1564],{"class":673,"line":1554},50,[671,1556,685],{"class":684},[671,1558,1559],{"class":688}," Receipt",[671,1561,693],{"class":692},[671,1563,697],{"class":696},[671,1565,700],{"class":692},[671,1567,1569],{"class":673,"line":1568},51,[671,1570,1571],{"class":758},"    \"\"\"model_serializer takes over the whole model's output shape.\"\"\"\n",[671,1573,1575],{"class":673,"line":1574},52,[671,1576,719],{"emptyLinePlaceholder":718},[671,1578,1580,1582,1584],{"class":673,"line":1579},53,[671,1581,1414],{"class":696},[671,1583,1397],{"class":692},[671,1585,1419],{"class":696},[671,1587,1589],{"class":673,"line":1588},54,[671,1590,706],{"class":692},[671,1592,1594],{"class":673,"line":1593},55,[671,1595,712],{"class":692},[671,1597,1599],{"class":673,"line":1598},56,[671,1600,719],{"emptyLinePlaceholder":718},[671,1602,1604],{"class":673,"line":1603},57,[671,1605,1606],{"class":1252},"    @model_serializer\n",[671,1608,1610,1612,1615,1618,1620],{"class":673,"line":1609},58,[671,1611,1189],{"class":684},[671,1613,1614],{"class":1252}," to_envelope",[671,1616,1617],{"class":692},"(self) -> dict[",[671,1619,1198],{"class":696},[671,1621,1622],{"class":692},", Any]:\n",[671,1624,1626,1628],{"class":673,"line":1625},59,[671,1627,1481],{"class":684},[671,1629,743],{"class":692},[671,1631,1633,1636,1638,1641],{"class":673,"line":1632},60,[671,1634,1635],{"class":758},"            \"type\"",[671,1637,1397],{"class":692},[671,1639,1640],{"class":758},"\"receipt\"",[671,1642,780],{"class":692},[671,1644,1646,1649],{"class":673,"line":1645},61,[671,1647,1648],{"class":758},"            \"data\"",[671,1650,1651],{"class":692},": {\n",[671,1653,1655,1658,1660,1663],{"class":673,"line":1654},62,[671,1656,1657],{"class":758},"                \"id\"",[671,1659,1397],{"class":692},[671,1661,1662],{"class":696},"self",[671,1664,1665],{"class":692},".id,\n",[671,1667,1669,1672,1674,1676,1679,1681,1683,1685],{"class":673,"line":1668},63,[671,1670,1671],{"class":758},"                \"issued_at\"",[671,1673,1397],{"class":692},[671,1675,1662],{"class":696},[671,1677,1678],{"class":692},".issued_at.strftime(",[671,1680,759],{"class":758},[671,1682,762],{"class":684},[671,1684,765],{"class":758},[671,1686,768],{"class":692},[671,1688,1690,1693,1695,1698,1700,1702,1704,1707,1709,1711],{"class":673,"line":1689},64,[671,1691,1692],{"class":758},"                \"total\"",[671,1694,1397],{"class":692},[671,1696,1697],{"class":684},"f",[671,1699,1272],{"class":758},[671,1701,1275],{"class":684},[671,1703,1662],{"class":696},[671,1705,1706],{"class":692},".total",[671,1708,1281],{"class":684},[671,1710,1272],{"class":758},[671,1712,780],{"class":692},[671,1714,1716],{"class":673,"line":1715},65,[671,1717,1718],{"class":692},"            },\n",[671,1720,1722],{"class":673,"line":1721},66,[671,1723,786],{"class":692},[590,1725,1726,1727,616,1730,1733,1734,1737],{},"The endpoints call ",[603,1728,1729],{},"model_dump(mode=\"python\")",[603,1731,1732],{},"model_dump(mode=\"json\")"," on the same instance, wrapping the python-mode values in ",[603,1735,1736],{},"repr()"," so the transcript shows the true Python types rather than FastAPI's later JSON coercion of them.",[590,1739,1740,1741,1744],{},"Real output from ",[603,1742,1743],{},"_verify\u002Foutput\u002Fpyd-field-serializer.txt",", produced by running the app above:",[663,1746,1750],{"className":1747,"code":1749,"language":846,"meta":667},[1748],"language-text","$ GET \u002Finvoices\u002F1\u002Fdefault\n200 OK\n{\n  \"python_mode\": {\n    \"id\": \"1\",\n    \"issued_at\": \"datetime.datetime(2026, 7, 20, 9, 30, tzinfo=datetime.timezone.utc)\",\n    \"total\": \"Decimal('1249.507')\"\n  },\n  \"json_mode\": {\n    \"id\": 1,\n    \"issued_at\": \"2026-07-20T09:30:00Z\",\n    \"total\": \"1249.507\"\n  }\n}\n\n$ GET \u002Finvoices\u002F1\n200 OK\n{\n  \"python_mode\": {\n    \"id\": \"1\",\n    \"issued_at\": \"'2026-07-20T09:30:00Z'\",\n    \"total\": \"1249.507\",\n    \"balance\": \"'300.50 EUR'\"\n  },\n  \"json_mode\": {\n    \"id\": 1,\n    \"issued_at\": \"2026-07-20T09:30:00Z\",\n    \"total\": 1249.507,\n    \"balance\": \"300.50 EUR\"\n  }\n}\n\n$ GET \u002Finvoices\u002F1\u002Fraw-json\n200 OK\n{\n  \"model_dump_json\": \"{\\\"id\\\":1,\\\"issued_at\\\":\\\"2026-07-20T09:30:00Z\\\",\\\"total\\\":1249.507,\\\"balance\\\":\\\"300.50 EUR\\\"}\"\n}\n\n$ GET \u002Freceipts\u002F1\n200 OK\n{\n  \"type\": \"receipt\",\n  \"data\": {\n    \"id\": 1,\n    \"issued_at\": \"2026-07-20T09:30:00Z\",\n    \"total\": \"1249.51\"\n  }\n}\n",[603,1751,1749],{"__ignoreMap":667},[590,1753,1754],{},"Three things in that transcript are worth pausing on.",[590,1756,1757,1760,1761,1764,1765,616,1767,1769,1770,1773,1774,1776,1777,1780,1781,1784,1785,1788,1789,1791,1792,1794],{},[593,1758,1759],{},"The baseline is not what v1 produced."," With no serializer, ",[603,1762,1763],{},"mode=\"python\""," keeps the real ",[603,1766,800],{},[603,1768,803],{}," objects, and ",[603,1771,1772],{},"mode=\"json\""," renders the ",[603,1775,803],{}," as the ",[634,1778,1779],{},"string"," ",[603,1782,1783],{},"\"1249.507\""," — Pydantic v2's default, chosen to avoid float precision loss. A v1 codebase with ",[603,1786,1787],{},"{Decimal: float}"," in ",[603,1790,817],{}," was emitting a JSON number. If you delete the encoder dict and add nothing, every ",[603,1793,803],{}," in your API silently changes from number to string. That is a wire-format break your type checker will not catch.",[590,1796,1797,1800,1801,1804,1805,1808,1809,1811,1812,1814,1815,1817,1818,1820],{},[593,1798,1799],{},"The serializer applies to both modes."," In the second response, ",[603,1802,1803],{},"python_mode"," shows ",[603,1806,1807],{},"'2026-07-20T09:30:00Z'"," — a ",[603,1810,1198],{},", not a ",[603,1813,800],{},". The v1 encoder would have left a ",[603,1816,800],{}," there. This is usually what you want, but if internal code was relying on ",[603,1819,796],{}," returning real objects, it now gets strings.",[590,1822,1823,1829,1830,1833,1834,1837],{},[593,1824,1825,1828],{},[603,1826,1827],{},"model_serializer"," fully replaces the output."," The receipt response has no top-level ",[603,1831,1832],{},"id"," at all; the method's return value ",[634,1835,1836],{},"is"," the serialization. Field-level serializers on the same model would still run for values you reference inside it, but nothing outside your returned dict survives.",[655,1839,1841],{"id":1840},"choosing-between-the-three","Choosing Between the Three",[1843,1844,1845,1858],"table",{},[1846,1847,1848],"thead",{},[1849,1850,1851,1855],"tr",{},[1852,1853,1854],"th",{},"Situation",[1852,1856,1857],{},"Use",[1859,1860,1861,1872,1882,1891,1900],"tbody",{},[1849,1862,1863,1867],{},[1864,1865,1866],"td",{},"One field needs custom formatting",[1864,1868,1869],{},[603,1870,1871],{},"@field_serializer(\"field\")",[1849,1873,1874,1877],{},[1864,1875,1876],{},"Several fields of the same type in one model",[1864,1878,1879],{},[603,1880,1881],{},"@field_serializer(\"a\", \"b\")",[1849,1883,1884,1887],{},[1864,1885,1886],{},"A type that should always render the same way everywhere",[1864,1888,1889],{},[603,1890,631],{},[1849,1892,1893,1896],{},[1864,1894,1895],{},"The response envelope differs from the model structure",[1864,1897,1898],{},[603,1899,625],{},[1849,1901,1902,1905],{},[1864,1903,1904],{},"Only the JSON path should be affected",[1864,1906,1907],{},[603,1908,1909],{},"@field_serializer(..., when_used=\"json\")",[590,1911,1912,1913,1915,1916,1918,1919,1922,1923,1342,1926,1929,1930,1933,1934,1936,1937,1940],{},"That last row is the closest thing to a literal ",[603,1914,817],{}," replacement. ",[603,1917,1345],{}," accepts ",[603,1920,1921],{},"always"," (the default), ",[603,1924,1925],{},"unless-none",[603,1927,1928],{},"json",", and ",[603,1931,1932],{},"json-unless-none",". If you are migrating a large codebase and want to preserve v1's exact split — encoders on the JSON path, raw objects in ",[603,1935,796],{}," — set ",[603,1938,1939],{},"when_used=\"json\""," and the two paths diverge again the way they used to.",[655,1942,1944],{"id":1943},"edge-cases-and-gotchas","Edge Cases and Gotchas",[597,1946,1947,1968,1983,2006,2023],{},[600,1948,1949,1952,1953,1956,1957,1960,1961,1780,1964,1967],{},[593,1950,1951],{},"Unknown types raise, they do not fall back."," Without a serializer, an arbitrary class produces ",[603,1954,1955],{},"PydanticSerializationError: Unable to serialize unknown type",". There is no silent ",[603,1958,1959],{},"str()"," coercion. Set ",[603,1962,1963],{},"arbitrary_types_allowed",[634,1965,1966],{},"and"," attach a serializer; the config flag alone only gets you past validation.",[600,1969,1970,1780,1976,1979,1980,1982],{},[593,1971,1972,1973,1975],{},"Declare ",[603,1974,1335],{},".",[603,1977,1978],{},"PlainSerializer(fn, return_type=str)"," tells the schema generator what comes out, so OpenAPI advertises ",[603,1981,1779],{}," rather than guessing. Skipping it means the generated schema can contradict the actual response body.",[600,1984,1985,1991,1992,1994,1995,1997,1998,2001,2002,2005],{},[593,1986,1987,1988,1975],{},"Decorator order matters with ",[603,1989,1990],{},"@property"," A ",[603,1993,1016],{}," is not a classmethod — it takes ",[603,1996,1662],{},". Applying ",[603,1999,2000],{},"@classmethod"," to it, out of habit from ",[603,2003,2004],{},"field_validator",", fails at class definition.",[600,2007,2008,2016,2017,2019,2020,2022],{},[593,2009,2010,616,2012,2015],{},[603,2011,1827],{},[603,2013,2014],{},"response_model"," interact."," FastAPI validates the returned object against ",[603,2018,2014],{}," and then serializes; a ",[603,2021,1827],{}," that returns a shape not matching the declared response model will produce a response body that does not match your OpenAPI schema. Declare the envelope as the response model, or return a plain dict from the endpoint.",[600,2024,2025,2028],{},[593,2026,2027],{},"Inheritance."," A serializer defined on a base model applies to subclasses. A subclass that redeclares the field without redeclaring the serializer still inherits it, which is usually right and occasionally surprising.",[655,2030,2032],{"id":2031},"verification","Verification",[590,2034,2035],{},"Assert on both dump modes, because that is exactly where v1 and v2 differ:",[663,2037,2039],{"className":665,"code":2038,"language":643,"meta":667,"style":667},"def test_serializer_applies_to_both_modes():\n    inv = Invoice(\n        id=1,\n        issued_at=datetime(2026, 7, 20, 9, 30, tzinfo=timezone.utc),\n        total=Decimal(\"1249.507\"),\n        balance=Money(Decimal(\"300.5\"), \"EUR\"),\n    )\n    assert inv.model_dump(mode=\"python\")[\"issued_at\"] == \"2026-07-20T09:30:00Z\"\n    assert inv.model_dump(mode=\"json\")[\"total\"] == 1249.507\n",[603,2040,2041,2051,2061,2073,2116,2130,2151,2156,2186],{"__ignoreMap":667},[671,2042,2043,2045,2048],{"class":673,"line":674},[671,2044,1249],{"class":684},[671,2046,2047],{"class":1252}," test_serializer_applies_to_both_modes",[671,2049,2050],{"class":692},"():\n",[671,2052,2053,2056,2058],{"class":673,"line":681},[671,2054,2055],{"class":692},"    inv ",[671,2057,740],{"class":684},[671,2059,2060],{"class":692}," Invoice(\n",[671,2062,2063,2066,2068,2071],{"class":673,"line":703},[671,2064,2065],{"class":688},"        id",[671,2067,740],{"class":684},[671,2069,2070],{"class":696},"1",[671,2072,780],{"class":692},[671,2074,2075,2078,2080,2083,2086,2088,2091,2093,2096,2098,2101,2103,2106,2108,2111,2113],{"class":673,"line":709},[671,2076,2077],{"class":688},"        issued_at",[671,2079,740],{"class":684},[671,2081,2082],{"class":692},"datetime(",[671,2084,2085],{"class":696},"2026",[671,2087,1342],{"class":692},[671,2089,2090],{"class":696},"7",[671,2092,1342],{"class":692},[671,2094,2095],{"class":696},"20",[671,2097,1342],{"class":692},[671,2099,2100],{"class":696},"9",[671,2102,1342],{"class":692},[671,2104,2105],{"class":696},"30",[671,2107,1342],{"class":692},[671,2109,2110],{"class":688},"tzinfo",[671,2112,740],{"class":684},[671,2114,2115],{"class":692},"timezone.utc),\n",[671,2117,2118,2121,2123,2126,2128],{"class":673,"line":715},[671,2119,2120],{"class":688},"        total",[671,2122,740],{"class":684},[671,2124,2125],{"class":692},"Decimal(",[671,2127,1783],{"class":758},[671,2129,768],{"class":692},[671,2131,2132,2135,2137,2140,2143,2146,2149],{"class":673,"line":722},[671,2133,2134],{"class":688},"        balance",[671,2136,740],{"class":684},[671,2138,2139],{"class":692},"Money(Decimal(",[671,2141,2142],{"class":758},"\"300.5\"",[671,2144,2145],{"class":692},"), ",[671,2147,2148],{"class":758},"\"EUR\"",[671,2150,768],{"class":692},[671,2152,2153],{"class":673,"line":734},[671,2154,2155],{"class":692},"    )\n",[671,2157,2158,2161,2164,2167,2169,2172,2175,2177,2180,2183],{"class":673,"line":746},[671,2159,2160],{"class":684},"    assert",[671,2162,2163],{"class":692}," inv.model_dump(",[671,2165,2166],{"class":688},"mode",[671,2168,740],{"class":684},[671,2170,2171],{"class":758},"\"python\"",[671,2173,2174],{"class":692},")[",[671,2176,1457],{"class":758},[671,2178,2179],{"class":692},"] ",[671,2181,2182],{"class":684},"==",[671,2184,2185],{"class":758}," \"2026-07-20T09:30:00Z\"\n",[671,2187,2188,2190,2192,2194,2196,2199,2201,2203,2205,2207],{"class":673,"line":771},[671,2189,2160],{"class":684},[671,2191,2163],{"class":692},[671,2193,2166],{"class":688},[671,2195,740],{"class":684},[671,2197,2198],{"class":758},"\"json\"",[671,2200,2174],{"class":692},[671,2202,1513],{"class":758},[671,2204,2179],{"class":692},[671,2206,2182],{"class":684},[671,2208,2209],{"class":696}," 1249.507\n",[590,2211,2212,2213,2215],{},"The higher-value test during a migration is a golden-file contract test: capture a response body from the v1 service, and assert the v2 service produces byte-identical JSON for the same input. The ",[603,2214,803],{},"-to-string change above is the kind of drift only that test catches.",[655,2217,2219],{"id":2218},"trade-offs-and-when-not-to","Trade-offs and When Not To",[590,2221,2222,2223,2226,2227,2229,2230,2232,2233,2235],{},"Serializers are logic on the response path, and logic on the response path is easy to forget. If a field's presentation is a ",[634,2224,2225],{},"transport"," concern rather than a property of the data, consider giving the endpoint a dedicated response model with a plain ",[603,2228,1198],{}," field and formatting in the service layer instead. That keeps the domain model honest and makes the wire format visible in the type, at the cost of one more class. For genuinely type-intrinsic formatting — money, durations, opaque IDs — ",[603,2231,1032],{}," on an ",[603,2234,1036],{}," type is the better home, because it cannot be forgotten.",[590,2237,2238,2239,2241,2242,2245],{},"Do not reach for ",[603,2240,625],{}," to add or rename a couple of fields; a ",[603,2243,2244],{},"computed_field"," and a field alias do that with less code and keep the schema accurate. Reserve it for cases where the wire shape is genuinely a different structure from the model.",[655,2247,2249],{"id":2248},"faq","FAQ",[590,2251,2252,2255,2256,616,2259,2262],{},[593,2253,2254],{},"Why was json_encoders removed in Pydantic v2?","\nIt was a Python-side post-processing dict applied only on the JSON path, so ",[603,2257,2258],{},"model_dump",[603,2260,2261],{},"model_dump_json"," could disagree, and it could not be inspected by the schema generator. Pydantic v2 moves serialization into the Rust core, where a serializer is attached to a field or a type and participates in both dump modes and in JSON Schema generation.",[590,2264,2265,2268,2271,2272,2274,2275,2277,2278,2280],{},[593,2266,2267],{},"What is the difference between field_serializer and model_serializer?",[603,2269,2270],{},"field_serializer"," replaces the output of one named field and leaves the rest of the model alone. ",[603,2273,1827],{}," replaces the entire output of the model, so you return the complete dict yourself. Use ",[603,2276,2270],{}," for type formatting and ",[603,2279,1827],{}," when the wire shape differs structurally from the model.",[590,2282,2283,2286,2287,2289,2290,2292],{},[593,2284,2285],{},"When should I use PlainSerializer in an Annotated type instead of a decorator?","\nWhen the rule belongs to the type rather than to one model. ",[603,2288,1032],{}," attached through ",[603,2291,1036],{}," travels with the type, so every field and every model using that type gets the same output without repeating a decorator.",[590,2294,2295,2298,2299,616,2301,2303,2304,2306,2307,1342,2309,1342,2311,1929,2313,2315],{},[593,2296,2297],{},"Does a field_serializer affect model_dump as well as model_dump_json?","\nBy default yes, in both ",[603,2300,643],{},[603,2302,1928],{}," mode. You can restrict it with ",[603,2305,1345],{},", which accepts ",[603,2308,1921],{},[603,2310,1925],{},[603,2312,1928],{},[603,2314,1932],{},", so a serializer can apply only on the JSON path if that is genuinely what you want.",[590,2317,2318,2321,2322,2324,2325,2327,2328,2330,2331,2333,2334,2336,2337,2340],{},[593,2319,2320],{},"How do I serialize a type Pydantic does not know at all?","\nSet ",[603,2323,1963],{}," on the model and attach a ",[603,2326,1032],{}," through ",[603,2329,1036],{},", or write a ",[603,2332,2270],{}," with an explicit ",[603,2335,1335],{},". Without one of those, serialization raises ",[603,2338,2339],{},"PydanticSerializationError"," for the unknown type.",[655,2342,2344],{"id":2343},"related-reading","Related Reading",[597,2346,2347,2356,2362,2373],{},[600,2348,2349,2352,2353,2355],{},[593,2350,2351],{},"Up to the topic:"," the ",[649,2354,652],{"href":651}," covers the rest of the API surface that moved.",[600,2357,2358,2359,1975],{},"The other config key with no mechanical equivalent is covered in ",[649,2360,2361],{"href":821},"model_config vs class Config",[600,2363,2364,2365,616,2369,1975],{},"Validators moved at the same time as serializers — see ",[649,2366,2368],{"href":2367},"\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrate-validator-to-field-validator\u002F","migrating @validator to @field_validator",[649,2370,2372],{"href":2371},"\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Froot-validator-to-model-validator\u002F","@root_validator to @model_validator",[600,2374,2375,2376,2380,2381,2385],{},"Once serializers are in place, ",[649,2377,2379],{"href":2378},"\u002Fadvanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002F","nested model serialization"," explains how they compose through nested structures, and ",[649,2382,2384],{"href":2383},"\u002Fadvanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Fpydantic-model-serialization-performance\u002F","serialization performance"," covers the cost of adding Python callbacks to the Rust path.",[2387,2388,2389],"style",{},"html pre.shiki code .sFeEa, html code.shiki .sFeEa{--shiki-default:#66707B}html pre.shiki code .sTJeM, html code.shiki .sTJeM{--shiki-default:#A0111F}html pre.shiki code .sV4o_, html code.shiki .sV4o_{--shiki-default:#702C00}html pre.shiki code .sigWx, html code.shiki .sigWx{--shiki-default:#0E1116}html pre.shiki code .sacAq, html code.shiki .sacAq{--shiki-default:#023B95}html pre.shiki code .sYEJz, html code.shiki .sYEJz{--shiki-default:#032563}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s3dhs, html code.shiki .s3dhs{--shiki-default:#622CBC}",{"title":667,"searchDepth":681,"depth":681,"links":2391},[2392,2393,2394,2403,2404,2405,2406,2407,2408],{"id":657,"depth":681,"text":658},{"id":979,"depth":681,"text":980},{"id":994,"depth":681,"text":995,"children":2395},[2396,2397,2399,2401],{"id":999,"depth":703,"text":1000},{"id":1012,"depth":703,"text":2398},"2. Attach @field_serializer per field",{"id":1028,"depth":703,"text":2400},"3. Attach PlainSerializer to the type with Annotated",{"id":1046,"depth":703,"text":2402},"4. Use @model_serializer when the shape changes",{"id":1840,"depth":681,"text":1841},{"id":1943,"depth":681,"text":1944},{"id":2031,"depth":681,"text":2032},{"id":2218,"depth":681,"text":2219},{"id":2248,"depth":681,"text":2249},{"id":2343,"depth":681,"text":2344},"2026-07-20","Pydantic v2 removed Config.json_encoders. Replace it with field_serializer, model_serializer or an Annotated PlainSerializer, with real serialized output shown.","md",[2413,2415,2417,2419,2421],{"q":2254,"a":2414},"It was a Python-side post-processing dict applied only on the JSON path, so model_dump and model_dump_json could disagree, and it could not be inspected by the schema generator. Pydantic v2 moves serialization into the Rust core, where a serializer is attached to a field or a type and participates in both dump modes and in JSON Schema generation.",{"q":2267,"a":2416},"field_serializer replaces the output of one named field and leaves the rest of the model alone. model_serializer replaces the entire output of the model, so you return the complete dict yourself. Use field_serializer for type formatting and model_serializer when the wire shape differs structurally from the model.",{"q":2285,"a":2418},"When the rule belongs to the type rather than to one model. PlainSerializer attached through Annotated travels with the type, so every field and every model using that type gets the same output without repeating a decorator.",{"q":2297,"a":2420},"By default yes, in both python and json mode. You can restrict it with when_used, which accepts always, unless-none, json, and json-unless-none, so a serializer can apply only on the JSON path if that is genuinely what you want.",{"q":2320,"a":2422},"Set arbitrary_types_allowed on the model and attach a PlainSerializer through Annotated, or write a field_serializer with an explicit return_type. Without one of those, serialization raises PydanticSerializationError for the unknown type.",null,{"slug":2425,"breadcrumb":2426},"replacing-json-encoders-with-field-serializer",[2427,2430,2433,2434],{"label":2428,"path":2429},"Home","\u002F",{"label":2431,"path":2432},"Advanced Pydantic Validation & Serialization","\u002Fadvanced-pydantic-validation-serialization\u002F",{"label":652,"path":651},{"label":2435,"path":2436},"Replacing json_encoders with field_serializer","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Freplacing-json-encoders-with-field-serializer\u002F",{"title":139,"description":2410},"article","sDgWBscFomp-FCElG7kmGKA2OSK-oiybleLpTjfafBQ",[2423,2423],1784588202620]