在从 pydantic.BaseModel 创建的 JSON 中,如果未设置,则排除可选

Arp*_*ath 4 python json python-3.x pydantic fastapi

我想排除所有在创建 JSON 时未设置的 Optional 值。在这个例子中:

from pydantic import BaseModel
from typing import Optional


class Foo(BaseModel):
    x: int
    y: int = 42
    z: Optional[int]


print(Foo(x=3).json())
Run Code Online (Sandbox Code Playgroud)

我明白了{"x": 3, "y": 42, "z": null}。但我想排除z. 不是因为它的值是None,而是因为它是 Optional 并且没有z. 在下面的两种情况下,我想z在 JSON 中使用。

Foo(x=1, z=None)
Foo(x=1, z=77)
Run Code Online (Sandbox Code Playgroud)

如果有任何其他解决方案z可以在这个意义上设置为可选,我希望看到它。

ale*_*ame 5

你可以排除未设置通过使该模型领域的联盟唯一可选的模型字段设置和那些不无

Pydantic 为导出方法model.dict(...)提供了以下参数:

exclude_unset: 创建模型时未明确设置的字段是否应从返回的字典中排除;默认False

exclude_none: 是否None应该从返回的字典中排除等于的字段;默认False

要合并两个字典,我们可以使用表达式a = {**b, **c}(来自 的c值覆盖来自 的值b)。请注意,从 Python 3.9 开始,它可以像a = b | c.

from pydantic import BaseModel
from typing import Optional
from pydantic.json import pydantic_encoder
import json


class Foo(BaseModel):
    x: int
    y: int = 42
    z: Optional[int]

def exclude_optional_dict(model: BaseModel):
    return {**model.dict(exclude_unset=True), **model.dict(exclude_none=True)}

def exclude_optional_json(model: BaseModel):
    return json.dumps(exclude_optional_dict(model), default=pydantic_encoder)
    


print(exclude_optional_json(Foo(x=3)))  # {"x": 3, "y": 42}
print(exclude_optional_json(Foo(x=3, z=None)))  # {"x": 3, "z": null, "y": 42}
print(exclude_optional_json(Foo(x=3, z=77)))  # {"x": 3, "z": 77, "y": 42}
Run Code Online (Sandbox Code Playgroud)

更新

为了使用嵌套模型的方法,我们需要对两个字典进行深度联合(或合并),如下所示:

def union(source, destination):
    for key, value in source.items():
        if isinstance(value, dict):
            node = destination.setdefault(key, {})
            union(value, node)
        else:
            destination[key] = value

    return destination

def exclude_optional_dict(model: BaseModel):
    return union(model.dict(exclude_unset=True), model.dict(exclude_none=True))

class Foo(BaseModel):
    x: int
    y: int = 42
    z: Optional[int]

class Bar(BaseModel):
    a: int
    b: int = 52
    c: Optional[int]
    d: Foo


print(exclude_optional_json(Bar(a=4, d=Foo(x=3))))
print(exclude_optional_json(Bar(a=4, c=None, d=Foo(x=3, z=None))))
print(exclude_optional_json(Bar(a=4, c=78, d=Foo(x=3, z=77))))
Run Code Online (Sandbox Code Playgroud)
{"a": 4, "b": 52, "d": {"x": 3, "y": 42}}
{"a": 4, "b": 52, "d": {"x": 3, "y": 42, "z": null}, "c": null}
{"a": 4, "b": 52, "c": 78, "d": {"x": 3, "y": 42, "z": 77}}
Run Code Online (Sandbox Code Playgroud)


Ros*_*ese 5

如果您使用 FastAPI,那么exclude_none当路由装饰器中提到response_model 时,使用似乎不起作用。

@app.post("/items/", response_model=Item)
async def create_item(item: Item):
    return item.dict(exclude_none=True)
Run Code Online (Sandbox Code Playgroud)

Fast api 似乎使用 pydantic 模型重新处理 dict

因此,重写模型本身中的 dict 方法应该可行

def Item(BaseModel):
   name: str
   description: Optional[str]
   ...
   def dict(self, *args, **kwargs) -> Dict[str, Any]:
        kwargs.pop('exclude_none', None)
        return super().dict(*args, exclude_none=True, **kwargs)
Run Code Online (Sandbox Code Playgroud)

(实际的解决方案会将此定义放在 BaseModel 的单独子类中以供重用)

注意:仅更改关键字参数的默认值exclude_none是不够的:FastAPI 似乎总是exclude_none=False作为参数发送。


来源:
https ://github.com/tiangolo/fastapi/issues/3314#issuecomment-962932368