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

问题描述

我想排除所有在创建 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())

我得到 {"x": 3,"y": 42,"z": null}。但我想排除z。不是因为它的值是 None,而是因为它是 Optional 并且 z 没有关键字参数。在下面的两种情况下,我希望在 JSON 中有 z

Foo(x=1,z=None)
Foo(x=1,z=77)

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

解决方法

您可以仅排除未设置的可选模型字段,方法是将已设置的模型字段与未设置的模型字段结合起来。

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,z=77)))  # {"x": 3,"z": 77,"y": 42}

更新

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

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,z=77))))
{"a": 4,"b": 52,"d": {"x": 3,"y": 42}}
{"a": 4,"y": 42,"z": null},"c": null}
{"a": 4,"c": 78,"z": 77}}