🗂

【Python】dataclassをネストされたdictから構築する【dataclass】

2022/07/19に公開

先にソリューション

@dataclass
class RecursiveDataclass:
    pass

    @classmethod
    def from_dict(cls, src: dict) -> RecursiveDataclass:
        kwargs = dict()
        field_dict: dict[str, Field] = {field.name: field for field in fields(cls)}
        field_type_dict: dict[str, type] = get_type_hints(cls)
        for src_key, src_value in src.items():
            assert src_key in field_dict, "Invalid Data Structure"
            field = field_dict[src_key]
            field_type = field_type_dict[field.name]
            if issubclass(field_type, RecursiveDataclass):
                kwargs[src_key] = field_type.from_dict(src_value)
            else:
                kwargs[src_key] = src_value
        return cls(**kwargs)


@dataclass
class Parent(RecursiveDataclass):
    field_a: str
    field_b: Child


@dataclass
class Child(RecursiveDataclass):
    field_c: int
    field_d: str


arg_dict = {"field_a": "hoge", "field_b": {"field_c": 3, "field_d": "fuga"}}
parent = Parent.from_dict(arg_dict)
print(parent)  # Parent(field_a='hoge', field_b=Child(field_c=3, field_d='fuga'))

dataclassはasdictで再帰的にdictにできるけど、dictから再帰的にdataclassを構築することはできない

dictからdataclassを使うにはkwargsの展開YourClass(**dict)が使われるけど、再帰的に展開はしてくれなずただ単にdictになる。

@dataclass
class Parent:
    field_a: str
    field_b: Child


@dataclass
class Child:
    field_c: int
    field_d: str


arg_dict = {"field_a": "hoge", "field_b": {"field_c": 3, "field_d": "fuga"}}
parent = Parent(**arg_dict)
# field_bがChildクラスのインスタンスではなく単なるdictとなってしまう。
print(parent)  # Parent(field_a='hoge', field_b={'field_c': 3, 'field_d': 'fuga'})

Full Code

from __future__ import annotations

from dataclasses import Field, dataclass, fields
from typing import get_type_hints


@dataclass
class RecursiveDataclass:
    pass

    @classmethod
    def from_dict(cls, src: dict) -> RecursiveDataclass:
        kwargs = dict()
        field_dict: dict[str, Field] = {field.name: field for field in fields(cls)}
        field_type_dict: dict[str, type] = get_type_hints(cls)
        for src_key, src_value in src.items():
            assert src_key in field_dict, "Invalid Data Structure"
            field = field_dict[src_key]
            field_type = field_type_dict[field.name]
            if issubclass(field_type, RecursiveDataclass):
                kwargs[src_key] = field_type.from_dict(src_value)
            else:
                kwargs[src_key] = src_value
        return cls(**kwargs)


@dataclass
class Parent(RecursiveDataclass):
    field_a: str
    field_b: Child


@dataclass(RecursiveDataclass)
class Child:
    field_c: int
    field_d: str


arg_dict = {"field_a": "hoge", "field_b": {"field_c": 3, "field_d": "fuga"}}
parent = Parent.from_dict(arg_dict)

Discussion