The dacite docs have a section about nested structures that is very close to what you want. The example they use, verbatim, is as follows:
@dataclass
class A:
x: str
y: int
@dataclass
class B:
a: A
data = {
'a': {
'x': 'test',
'y': 1,
}
}
result = from_dict(data_class=B, data=data)
assert result == B(a=A(x='test', y=1))
We can access fields at arbitrary depth as e.g. result.a.x == 'test'
.
The critical difference between this and your data is that the dictionary under the data
key has keys with arbitrary values (Aatrox
, Ahri
, etc.). dacite isn't set up to create new field names on the fly, so the best you're going to get is something like the latter part of @JonSG's answer, which uses setattr
to dynamically build new fields.
Let's imagine how you would use this data for a moment, though. Probably you'd want a some point to be able to iterate over your champions in order to perform a filter/transform/etc. operation. It's possible to iterate over fields in python, but you have to really dig into python internals, which means your code will be less readable/generally comprehensible.
Much better would be one of the following:
- Preprocess
j1
into a shape that fits the structure you want to use, and then use dacite with a dataclass
that fits the new structure. For example, maybe it makes sense to pull the values of the data
dict out into a list.
- Process in steps using dacite. For example, something like the following:
from dataclasses import dataclass
from dacite import from_dict
@dataclass
class TopLevel:
type: str
data: dict
j1 = {
"type": "champion",
"data": {
"Aatrox": {"id": "Aatrox", "key": "266", "name": "Aatrox"},
"Ahri": {"id": "Ahri", "key": "103", "name": "Ahri"},
},
}
champions = from_dict(data_class=TopLevel, data=j1)
# champions.data is a dict of dicts
@dataclass
class Champion:
id: str
key: str
name: str
# transform champions.data into a dict of Champions
for k, v in champions.data.items():
champions.data[k] = from_dict(data_class=Champion, data=v)
# now, you can do interesting things like the following filter operation
start_with_a = [
champ for champ in champions.data.values() if champ.name.lower().startswith("a")
]
print(start_with_a)
# [Champion(id='Aatrox', key='266', name='Aatrox'), Champion(id='Ahri', key='103', name='Ahri')]