使用 Pydantic 解包嵌套 JSON 时跳过字典级别

Skipping dict levels when unpacking nested JSON with Pydantic

我正在从 API 中获取并得到 JSON 响应,如下所示:

 {
      "features": {
        "due_dates": {
          "enabled": true,
          "start_date": false,
          "remap_due_dates": true,
          "remap_closed_due_date": false
        },
        "sprints": {
          "enabled": false
        },
        "points": {
          "enabled": false
        },
        "custom_items": {
          "enabled": false
        },
        "tags": {
          "enabled": true
        },
        "time_estimates": {
          "enabled": true
        },
        "checklists": {
          "enabled": true
        },
        "zoom": {
          "enabled": false
        },
        "milestones": {
          "enabled": false
        },
        "custom_fields": {
          "enabled": true
        },
        "remap_dependencies": {
          "enabled": true
        },
        "dependency_warning": {
          "enabled": true
        },
        "multiple_assignees": {
          "enabled": true
        },
        "portfolios": {
          "enabled": true
        },
        "emails": {
          "enabled": true
        }
      }

我的 class 设置如下:

class Features(BaseModel):
    multiple_assignees: bool = False
    start_date: bool = False
    remap_due_dates: bool = False
    remap_closed_due_date: bool = False
    time_tracking: bool = False
    tags: bool = False
    time_estimates: bool = False
    checklists: bool = False
    custom_fields: bool = False
    remap_dependencies: bool = False
    dependency_warning: bool = False
    portfolios: bool = False
    points: bool = False
    custom_items: bool = False
    zoom: bool = False
    milestones: bool = False
    emails: bool = False

我想做的是将此 JSON 解压缩到功能对象中,并为每个相应的属性使用“enabled”键中的值。我想这样做以避免必须为冲刺、积分、custom_items 等设置 class。是否可以只获取嵌套的“已启用”布尔值并跳过设置每个 class?

不幸的是,我认为没有一种非常简单的方法可以做到这一点。我想最直接的方法是使用 this issue 中提到的根验证器,这样我们就可以使用我们自己想要的逻辑来解包字段。像这样的东西应该适用于:

import json

from pydantic import BaseModel, root_validator


class Features(BaseModel):
    multiple_assignees: bool = False
    start_date: bool = False
    remap_due_dates: bool = False
    remap_closed_due_date: bool = False
    time_tracking: bool = False
    tags: bool = False
    time_estimates: bool = False
    checklists: bool = False
    custom_fields: bool = False
    remap_dependencies: bool = False
    dependency_warning: bool = False
    portfolios: bool = False
    points: bool = False
    custom_items: bool = False
    zoom: bool = False
    milestones: bool = False
    emails: bool = False

    @root_validator(pre=True)
    def extract_features(cls, v):
        # Unwrap features and all 'enabled' values for each field within
        res = {k: v['enabled'] if isinstance(v, dict) and 'enabled' in v else v
               for k, v in v['features'].items()}
        # Map due date fields which are a bit different
        due_dates = v['features'].get('due_dates')
        if due_dates:
            for field in ('start_date',
                          'remap_due_dates',
                          'remap_closed_due_date'):
                res[field] = due_dates.get(field, False)

        return res


string = """
 {
      "features": {
        "due_dates": {
          "enabled": true,
          "start_date": false,
          "remap_due_dates": true,
          "remap_closed_due_date": false
        },
        "sprints": {
          "enabled": false
        },
        "points": {
          "enabled": false
        },
        "custom_items": {
          "enabled": false
        },
        "tags": {
          "enabled": true
        },
        "time_estimates": {
          "enabled": true
        },
        "checklists": {
          "enabled": true
        },
        "zoom": {
          "enabled": false
        },
        "milestones": {
          "enabled": false
        },
        "custom_fields": {
          "enabled": true
        },
        "remap_dependencies": {
          "enabled": true
        },
        "dependency_warning": {
          "enabled": true
        },
        "multiple_assignees": {
          "enabled": true
        },
        "portfolios": {
          "enabled": true
        },
        "emails": {
          "enabled": true
        }
    }
}
"""

d = json.loads(string)

x = Features(**d)
print(x)