python ^3.7. Trying to create nested dataclasses to work with complex json response. I managed to do that with creating dataclass for every level of json and using __post_init_
to set fields as objects of other dataclasses. However that creates a lot of boilerplate code and also, there is no annotation for nested objects.
This answer helped me getting closer to the solution using wrapper:
https://mcmap.net/q/81343/-creating-nested-dataclass-objects-in-python
However it does not solve it for cases where attribute is list of objects. some_attribute: List[SomeClass]
Here is example that resembles my data:
from dataclasses import dataclass, is_dataclass
from typing import List
from copy import deepcopy
# decorator from the linked thread:
def nested_deco(*args, **kwargs):
def wrapper(check_class):
# passing class to investigate
check_class = dataclass(check_class, **kwargs)
o_init = check_class.__init__
def __init__(self, *args, **kwargs):
for name, value in kwargs.items():
# getting field type
ft = check_class.__annotations__.get(name, None)
if is_dataclass(ft) and isinstance(value, dict):
obj = ft(**value)
kwargs[name] = obj
o_init(self, *args, **kwargs)
check_class.__init__ = __init__
return check_class
return wrapper(args[0]) if args else wrapper
#some dummy dataclasses to resemble my data structure
@dataclass
class IterationData:
question1: str
question2: str
@nested_deco
@dataclass
class IterationResult:
name: str
data: IterationData
@nested_deco
@dataclass
class IterationResults:
iterations: List[IterationResult]
@dataclass
class InstanceData:
date: str
owner: str
@nested_deco
@dataclass
class Instance:
data: InstanceData
name: str
@nested_deco
@dataclass
class Result:
status: str
iteration_results: IterationResults
@nested_deco
@dataclass
class MergedInstance:
instance: Instance
result: Result
#example data
single_instance = {
"instance": {
"name": "example1",
"data": {
"date": "2021-01-01",
"owner": "Maciek"
}
},
"result": {
"status": "complete",
"iteration_results": [
{
"name": "first",
"data": {
"question1": "yes",
"question2": "no"
}
}
]
}
}
instances = [deepcopy(single_instance) for i in range(3)] #created a list just to resemble mydata
objres = [MergedInstance(**inst) for inst in instances]
As you will notice. nested_deco
works perfectly for attributes of MergedInstance
and for attribute data
of Instance
but it does not load IterationResults
class on iteration_results
of Result
.
Is there a way to achieve it?
I attach also example with my post_init solution which creates objects of classes but there is no annotation of attributes:
@dataclass
class IterationData:
question1: str
question2: str
@dataclass
class IterationResult:
name: str
data: dict
def __post_init__(self):
self.data = IterationData(**self.data)
@dataclass
class InstanceData:
date: str
owner: str
@dataclass
class Instance:
data: dict
name: str
def __post_init__(self):
self.data = InstanceData(**self.data)
@dataclass
class Result:
status: str
iteration_results: list
def __post_init__(self):
self.iteration_results = [IterationResult(**res) for res in self.iteration_results]
@dataclass
class MergedInstance:
instance: dict
result: dict
def __post_init__(self):
self.instance = Instance(**self.instance)
self.result = Result(**self.result)
pydantic
is a good solution, however the only problem as I remember was mentioned somewhere is that it introduces package bloat - for ex. you might not need validations or even a replacement for dataclasses. I also likedataclasses-json
for what it provides as well as good docs, however it's important to note that it introduces other 3rd party dependencies you might not need (for ex. marshmallow) and as a result it's slower than pydantic. I would suggest pydantic over dataclasses-json if given a choice personally - in case it wasn't obvious I'm not a fan of marshmallow in general. – Chalcedony