---
title: "Pydantic exclude_none vs Intentional null Fields"
source: daily/2026-05-10.md
updated: 2026-05-10
tags: [pydantic, python, fastapi, mongodb, serialization]
---

# Pydantic `exclude_none` vs Intentional `null` Fields

## Rule

`model_dump(exclude_none=True)` removes **all** fields whose value is `None`, including fields that the caller **intentionally set to `null`** to clear a value in the database.

## Problem

```python
class UpdateModel(BaseModel):
    name: str | None = None
    image_url: str | None = None
    description: str | None = None

# User wants to clear the image_url field → sets it to null
update_data = UpdateModel(name="New Name", image_url=None, description=None)

# BUG: exclude_none removes image_url entirely
patch = update_data.model_dump(exclude_none=True)
# Result: {"name": "New Name"}
# image_url is missing → MongoDB $set won't touch it → field not cleared
```

The field silently fails to update. No error is raised.

## Fix

Remove `exclude_none=True`. Instead, **explicitly list only the fields you want to include**:

```python
# Option A: explicit field list
patch = {
    "name": update_data.name,
    "image_url": update_data.image_url,
}
# Only include fields that were explicitly passed (check for MISSING sentinel if needed)

# Option B: use model_fields_set to send only what was explicitly provided
patch = update_data.model_dump(include=update_data.model_fields_set)
```

`model_fields_set` contains only the fields that were explicitly set by the caller, not default values — this is usually exactly what a PATCH endpoint needs.

## When `exclude_none` Is Safe

Use `exclude_none=True` only when:
- `None` **always** means "not provided" (i.e., `None` is never a valid clearing value)
- You are building a CREATE payload, not a PATCH payload
- All optional fields have database-level defaults and you never need to explicitly null them

## MongoDB PATCH Pattern

```python
@router.patch("/{id}")
async def update_item(id: str, body: UpdateBody):
    # Only send fields the client explicitly included in the request
    update_fields = body.model_dump(include=body.model_fields_set)
    if not update_fields:
        raise HTTPException(400, "No fields to update")
    await collection.update_one({"_id": ObjectId(id)}, {"$set": update_fields})
```
