You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在FastAPI+MongoDB环境下用Pydantic更新嵌套模型

MongoDB数组元素更新异常问题

问题场景

我尝试更新用户文档中指定名称的角色信息,执行后却在characters数组里新增了一个独立元素,而非修改目标角色的字段。

我的Pydantic模型

class User(BaseModel):
    id: int = Field(default_factory=uuid.UUID, alias="_id")
    post_count: int
    last_awarded: int
    points: int
    characters: list[Character]

class CharacterUpdate(BaseModel):
    name: Optional[str] = None
    type: Optional[str] = None
    xp: Optional[int] = None

FastAPI路由代码

@router.put("/{id}/{name}", response_description="Update a character", response_model=Character)
def update_character(id: int, name:str, request: Request, char: CharacterUpdate = Body(...)):
    char = {k: v for k, v in char.model_dump().items() if v is not None}
    if len(char) >= 1:
        update_result = request.app.database["users"].update_one(
            {"_id": id, "characters.name": name}, {"$set": char}
        )

原始MongoDB用户文档

{
  "_id": 12345,
  "post_count": 0,
  "last_awarded": 0,
  "points": 0,
  "characters": [
    {
      "name": "Person 1",
      "type": "Character",
      "xp": 900
    },
    {
      "name": "Person 2",
      "type": "NPC",
      "xp": 900
    }
  ]
}

测试用curl请求

curl -X 'PUT' \
  'http://localhost:8000/user/12345/Person%201' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "name": "Person 3"
}'

预期与实际结果

  • 预期:将Person 1的name字段改为Person 3,保留原有type和xp字段
  • 实际:characters数组新增了独立元素"name": "Person 3",原角色未修改:
{
  "_id": 12345,
  "post_count": 0,
  "last_awarded": 0,
  "points": 0,
  "characters": [
    {
      "name": "Person 1",
      "type": "Character",
      "xp": 900
    },
    {
      "name": "Person 2",
      "type": "NPC",
      "xp": 900
    },
    "name": "Person 3"
  ]
}

需求:路由需支持动态更新——比如请求修改type和xp时,保留name不变,以此类推。


解决方案

问题根源是MongoDB的$set操作符使用错误:直接传入字段键值对时,MongoDB无法识别要更新数组中的嵌套元素,会把内容直接添加到数组或文档根级别。需要用**位置操作符$**定位匹配到的数组元素。

修改后的路由代码:

from fastapi import HTTPException

@router.put("/{id}/{name}", response_description="Update a character", response_model=Character)
def update_character(id: int, name:str, request: Request, char: CharacterUpdate = Body(...)):
    # 过滤空值,只保留需要更新的字段
    update_data = {k: v for k, v in char.model_dump().items() if v is not None}
    if not update_data:
        raise HTTPException(status_code=400, detail="没有需要更新的字段")
    
    # 构造$set的目标字段,用characters.$.字段名定位数组中的匹配元素
    set_fields = {f"characters.$.{key}": value for key, value in update_data.items()}
    
    update_result = request.app.database["users"].update_one(
        {"_id": id, "characters.name": name}, 
        {"$set": set_fields}
    )
    
    # 检查是否匹配到目标文档
    if update_result.matched_count == 0:
        raise HTTPException(status_code=404, detail="用户或角色不存在")
    
    # 查询并返回更新后的角色信息
    updated_user = request.app.database["users"].find_one({"_id": id})
    updated_char = next(c for c in updated_user["characters"] if c["name"] == update_data.get("name", name))
    return updated_char

关键说明

  1. 位置操作符$:MongoDB会自动将$替换为匹配到的数组元素索引,确保$set精准修改目标元素的指定字段,而非新增元素。
  2. 动态构造更新字段:把请求中的每个字段转换为characters.$.字段名格式,保证所有更新都作用于数组内的目标角色。
  3. 结果校验:增加匹配计数检查,避免无提示失败;最后查询返回更新后的角色,符合响应模型要求。

修改后执行原curl请求,即可正确将Person 1的name改为Person 3,且保留原有type和xp。若请求修改type或xp,也只会更新对应字段,不影响其他内容。

内容的提问来源于stack exchange,提问作者SinicalDreams

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 22:43:26