I am working on an JSON API-call and I wanted to build it using python data classes. Since I am a beginner the solution may be cumbersome - in that case - please advise a more elegant solution.
So I want my JSON request body to look like this:
{
"projectFilter": {
"project": "all",
"statuses": ["Finished"]
},
"currencyId": 0,
"columns": [
{"columnType": {
"kind": "CostActual"
}
}
]
}
I have modeled this as a nested data class so I can change the API calls in the future. Not sure if this a good way - as I said - beginner here.
import json
from dataclasses import dataclass, field
from typing import List, Dict
@dataclass
class _project_filter():
project : str = 'all'
statuses : List = field(default_factory=lambda: ['Finished'])
@dataclass
class _column_type():
columnType : Dict = field(default_factory=lambda: {'kind' : 'CostActual'})
@dataclass
class project():
projectFilter: _project_filter = _project_filter()
currencyId : int = NULL
columns : List[_column_type] = field(default_factory=_column_type)
def to_json(self):
return json.dumps(self.__dict__, default=lambda x: x.__dict__, ensure_ascii=False)
I get the following output when I call the .to_json()
method:
{
"projectFilter": {
"project": "all",
"statuses": ["Finished"]
},
"currencyId": 0,
"columns": {
"columnType": {"kind": "CostActual"}
}
}
As you can see I don't get an array of the type _column_type
.
I thought it would work since I have columns : List[_column_type] = field(default_factory=_column_type)
Where I though i specified the field columns
as a list
of _column_type
and that I used the default object since I use the default_factory
argument.
I would suggest checking out the dataclass-wizard
library, as it seems like it'd be a perfect fit for this use case.
The default case transform used for serialization purposes is camelCase, so the good news is you don't need to pass any additional config to get the desired result when serializing an instance to JSON.
I started off by piping the sample JSON input to the included CLI utility in order to generate a dataclass schema, and ended up with a dataclass model strucure as below. Note that I went in and added default values for a few fields, similar to how you had it above.
from __future__ import annotations
from dataclasses import dataclass, field
from dataclass_wizard import JSONWizard
@dataclass
class Project(JSONWizard):
project_filter: ProjectFilter
currency_id: int
columns: list[Column]
@dataclass
class ProjectFilter:
project: str = 'all'
statuses: list[str] = field(default_factory=lambda: ['Finished'])
@dataclass
class Column:
column_type: ColumnType | None = None
@dataclass
class ColumnType:
kind: str = 'CostActual'
Then I did a quick test to confirm that I'm able to load/dump JSON data using the dataclass schema:
def main():
string = """
{
"projectFilter": {
"project": "all"
},
"currencyId": 0,
"columns": [
{"columnType": {}}
]
}"""
p = Project.from_json(string)
print(repr(p))
print(p.to_json())
# True
assert p == p.from_json(p.to_json())
if __name__ == '__main__':
main()
Output:
Project(project_filter=ProjectFilter(project='all', statuses=['Finished']), currency_id=0, columns=[Column(column_type=ColumnType(kind='CostActual'))])
{"projectFilter": {"project": "all", "statuses": ["Finished"]}, "currencyId": 0, "columns": [{"columnType": {"kind": "CostActual"}}]}