【问题标题】:Python/Pydantic - using a list with json objectsPython/Pydantic - 使用带有 json 对象的列表
【发布时间】:2020-01-23 20:26:53
【问题描述】:

我有一个使用pydantic 接收json 数据集的工作模型。模型数据集如下所示:

data = {'thing_number': 123, 
        'thing_description': 'duck',
        'thing_amount': 4.56}

我想做的是将json 文件列表作为数据集并能够验证它们。最终该列表将转换为pandas 中的记录以供进一步处理。我的目标是验证一个任意长的json 条目列表,看起来像这样:

bigger_data = [{'thing_number': 123, 
                'thing_description': 'duck',
                'thing_amount': 4.56}, 
               {'thing_number': 456, 
                'thing_description': 'cow',
                'thing_amount': 7.89}]

我现在的基本设置如下。请注意,添加 class ItemList 是尝试使任意长度起作用的一部分。

from typing import List
from pydantic import BaseModel
from pydantic.schema import schema
import json

class Item(BaseModel):
    thing_number: int
    thing_description: str
    thing_amount: float

class ItemList(BaseModel):
    each_item: List[Item]                                                                           

然后,基本代码将在数组对象中生成我认为我正在寻找的内容,该对象将采用 Item 对象。

item_schema = schema([ItemList])
print(json.dumps(item_schema, indent=2)) 

    {
      "definitions": {
        "Item": {
          "title": "Item",
          "type": "object",
          "properties": {
            "thing_number": {
              "title": "Thing_Number",
              "type": "integer"
            },
            "thing_description": {
              "title": "Thing_Description",
              "type": "string"
            },
            "thing_amount": {
              "title": "Thing_Amount",
              "type": "number"
            }
          },
          "required": [
            "thing_number",
            "thing_description",
            "thing_amount"
          ]
        },
        "ItemList": {
          "title": "ItemList",
          "type": "object",
          "properties": {
            "each_item": {
              "title": "Each_Item",
              "type": "array",
              "items": {
                "$ref": "#/definitions/Item"
              }
            }
          },
          "required": [
            "each_item"
          ]
        }
      }
    }

该设置适用于正在传递的单个 json 项:

item = Item(**data)                                                      

print(item)

Item thing_number=123 thing_description='duck' thing_amount=4.56

但是当我尝试将单个项目传递给 ItemList 模型时,它会返回错误:

item_list = ItemList(**data)

---------------------------------------------------------------------------
ValidationError                           Traceback (most recent call last)
<ipython-input-94-48efd56e7b6c> in <module>
----> 1 item_list = ItemList(**data)

/opt/conda/lib/python3.7/site-packages/pydantic/main.cpython-37m-x86_64-linux-gnu.so in pydantic.main.BaseModel.__init__()

/opt/conda/lib/python3.7/site-packages/pydantic/main.cpython-37m-x86_64-linux-gnu.so in pydantic.main.validate_model()

ValidationError: 1 validation error for ItemList
each_item
  field required (type=value_error.missing)

我还尝试将bigger_data 传递到数组中,认为它需要以列表形式开始。这也会返回一个错误 - - 虽然,我至少对字典错误有了更好的理解,但我不知道如何解决。

item_list2 = ItemList(**data_big)

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-100-8fe9a5414bd6> in <module>
----> 1 item_list2 = ItemList(**data_big)

TypeError: MetaModel object argument after ** must be a mapping, not list

谢谢。

我尝试过的其他事情

我已经尝试将数据传递到特定键中,但运气更好(也许?)。

item_list2 = ItemList(each_item=data_big)

---------------------------------------------------------------------------
ValidationError                           Traceback (most recent call last)
<ipython-input-111-07e5c12bf8b4> in <module>
----> 1 item_list2 = ItemList(each_item=data_big)

/opt/conda/lib/python3.7/site-packages/pydantic/main.cpython-37m-x86_64-linux-gnu.so in pydantic.main.BaseModel.__init__()

/opt/conda/lib/python3.7/site-packages/pydantic/main.cpython-37m-x86_64-linux-gnu.so in pydantic.main.validate_model()

ValidationError: 6 validation errors for ItemList
each_item -> 0 -> thing_number
  field required (type=value_error.missing)
each_item -> 0 -> thing_description
  field required (type=value_error.missing)
each_item -> 0 -> thing_amount
  field required (type=value_error.missing)
each_item -> 1 -> thing_number
  field required (type=value_error.missing)
each_item -> 1 -> thing_description
  field required (type=value_error.missing)
each_item -> 1 -> thing_amount
  field required (type=value_error.missing)

【问题讨论】:

    标签: python json pydantic


    【解决方案1】:

    以下也可以,并且不需要根类型。

    List[dict] 转换为List[Item]

    items = parse_obj_as(List[Item], bigger_data)
    

    从 JSON str 转换为 List[Item]

    items = parse_raw_as(List[Item], bigger_data_json)
    

    List[Item] 转换为 JSON str

    bigger_data_json = json.dumps(items, default=pydantic_encoder)
    

    或使用自定义编码器:

    def custom_encoder(**kwargs):
        def base_encoder(obj):
            if isinstance(obj, BaseModel):
                return obj.dict(**kwargs)
            else:
                return pydantic_encoder(obj)
        return base_encoder
    
    
    bigger_data_json = json.dumps(items, default=custom_encoder(by_alias=True))
    

    【讨论】:

    • 我发现这真的很有用。对于其他人,pydantic_encoder 的导入是:from pydantic.json import pydantic_encoder
    • 这为我节省了一天。这正是我所需要的,如果不这样做,我会收到一个不可序列化的数据模型错误。
    【解决方案2】:

    为避免在ItemList 中包含"each_item",您可以使用__root__ Pydantic 关键字:

    from typing import List
    from pydantic import BaseModel
    
    class Item(BaseModel):
        thing_number: int
        thing_description: str
        thing_amount: float
    
    class ItemList(BaseModel):
        __root__: List[Item]    # ⯇-- __root__
    

    构建item_list

    just_data = [
        {"thing_number": 123, "thing_description": "duck", "thing_amount": 4.56},
        {"thing_number": 456, "thing_description": "cow", "thing_amount": 7.89},
    ]
    item_list = ItemList(__root__=just_data)
    
    a_json_duck = {"thing_number": 123, "thing_description": "duck", "thing_amount": 4.56}
    item_list.__root__.append(a_json_duck)
    

    支持 Pydantic 的 web 框架经常将 ItemList jsonify 为 JSON 数组,没有中间的 __root__ 关键字。

    【讨论】:

    • 就我自己的理解而言,__root__ 是否有效地将ItemList 的“根”字符更改为Item 中的那些项目?然而,使用each_item 有效地在ItemList 中创建了一个东西?谢谢。
    • docs 将其列为一个用例,所以我更喜欢这个,虽然让用户使用__root__ 关键字感觉有点不符合pythonic。此方法相对于other answer 的优势在于ItemList.json() 返回预期的JSON 结构。
    • 不幸的是,当将项目附加到 root 时(如本答案末尾所做的那样),这些项目没有得到验证。它们只是简单地附加。如果(如您的情况),列表中的项目是可能需要验证的 pydantic 模型,您需要自己触发它(例如使用 Item.validate(...))。
    • 有关信息,如果您想遍历__root__ 列表或通过索引访问项目——您必须在类中实现__iter____getitem__ 方法。
    【解决方案3】:
    from typing import List
    from pydantic import BaseModel
    import json
    
    
    class Item(BaseModel):
        thing_number: int
        thing_description: str
        thing_amount: float
    
    
    class ItemList(BaseModel):
        each_item: List[Item]
    

    基于您的代码,将 each_item 作为项目列表

    a_duck = Item(thing_number=123, thing_description="duck", thing_amount=4.56)
    print(a_duck.json())
    
    a_list = ItemList(each_item=[a_duck])
    
    print(a_list.json())
    

    生成以下输出:

    {"thing_number": 123, "thing_description": "duck", "thing_amount": 4.56}
    {"each_item": [{"thing_number": 123, "thing_description": "duck", "thing_amount": 4.56}]}
    

    将这些用作“入口 json”:

    a_json_duck = {"thing_number": 123, "thing_description": "duck", "thing_amount": 4.56}
    a_json_list = {
        "each_item": [
            {"thing_number": 123, "thing_description": "duck", "thing_amount": 4.56}
        ]
    }
    
    print(Item(**a_json_duck))
    print(ItemList(**a_json_list))
    

    工作正常并生成:

    Item thing_number=123 thing_description='duck' thing_amount=4.56
    ItemList each_item=[<Item thing_number=123 thing_description='duck' thing_amount=4.56>]
    

    我们只剩下唯一的数据了:

    just_datas = [
        {"thing_number": 123, "thing_description": "duck", "thing_amount": 4.56},
        {"thing_number": 456, "thing_description": "cow", "thing_amount": 7.89},
    ]
    item_list = ItemList(each_item=just_datas)
    print(item_list)
    print(type(item_list.each_item[1]))
    print(item_list.each_item[1])
    

    那些按预期工作:

    ItemList each_item=[<Item thing_number=123 thing_description='duck'thing_amount=4.56>,<Item thin…
    <class '__main__.Item'>
    Item thing_number=456 thing_description='cow' thing_amount=7.89
    

    因此,如果我遗漏了什么,pydantic 库会按预期工作。

    我的 pydantic 版本:0.30 python 3.7.4

    从相似文件中读取:

    json_data_file = """[
    {"thing_number": 123, "thing_description": "duck", "thing_amount": 4.56},
    {"thing_number": 456, "thing_description": "cow", "thing_amount": 7.89}]"""
    
    from io import StringIO
    item_list2 = ItemList(each_item=json.load(StringIO(json_data_file)))
    

    工作也很好。

    【讨论】:

    • 我花了几个小时思考问题出在类/对象结构上——而不是我加载信息的方式。完美运行。谢谢。
    猜你喜欢
    • 2021-06-22
    • 2021-12-21
    • 1970-01-01
    • 2014-11-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-19
    • 2017-02-08
    相关资源
    最近更新 更多