from dataclasses import dataclass, field
from typing import List, Dict, Any, Tuple, Type, TypeVar, Mapping, Union
import dacite
T = TypeVar("T")
# reconstruct data by changing data's key to new key from key_referrence in recursion
def _rekey_dict(key_referrence: Mapping, data: Mapping) -> Dict[str, Any]:
# _rekey_list is a helper function to rekey any sub dicts in list or tuple
def _rekey_list(key_referrence: Mapping, data: Union[List, Tuple]) -> List:
res = []
for v in data:
if isinstance(v, dict):
res.append(_rekey_dict(key_referrence, v))
elif isinstance(v, (List, Tuple)):
res.append(_rekey_list(key_referrence, v))
else:
res.append(v)
return res
res = {}
for k, v in data.items():
if k in key_referrence:
new_k = key_referrence[k]
else:
new_k = k
if isinstance(v, dict):
res[new_k] = _rekey_dict(key_referrence, v)
elif isinstance(v, (List, Tuple)):
res[new_k] = _rekey_list(key_referrence, v)
else:
res[new_k] = v
return res
def from_dict(data_class: Type[T], origin_data: dict, metadata_tag: str="") -> T:
if not metadata_tag:
return dacite.from_dict(data_class=data_class, data=origin_data)
# metadata_tag to class field mapping
tag2field = {}
for field_name, field_attr in data_class.__dataclass_fields__.items():
if metadata_tag in field_attr.metadata:
tag2field[field_attr.metadata[metadata_tag]] = field_name
else:
tag2field[field_name] = field_name
# print(tag2field)
final_data = _rekey_dict(tag2field, origin_data)
# print(final_data)
return dacite.from_dict(data_class=data_class, data=final_data)
@dataclass
class Address:
street: str = field(default="", metadata={"json_tag": "Street"})
number: int = field(default=0, metadata={"json_tag": "Number"})
@dataclass
class Person:
male: bool = field(default=True)
name: str = field(default="", metadata={"json_tag": "Name"})
age: int = field(default=0, metadata={"json_tag": "Age"})
address: List[Address] = field(default_factory=lambda: list(), metadata={"json_tag": "Address"})
if __name__ == "__main__":
data = {
"Name": "hh",
"Age": 10,
"Address": [{
"Street": "s1",
"Number": 99
}]
}
p = from_dict(data_class=Person, metadata_tag="json_tag", origin_data=data)
print(p) # Person(male=True, name='hh', age=10, address=[Address(street='', number=0)])
# 在from_dict中构造tag2field的时候目前只能拿到顶层field的metadata
# Person.address 嵌套的Address类内部字段Street和Number的metadata拿不到,所以`address=[Address(street='', number=0)]`
网友评论