Package: dict-to-dataclass
Table of contentsDict to dataclassWhy?Finding dictionary valuesNested data classesListsValue conversionDatetimeEnumCustom convertersOptional typesMissing valuesData validationDict to dataclassDict to dataclass makes it easy to convert dictionaries to instances of dataclasses.fromdataclassesimportdataclassfromdatetimeimportdatetimefromdict_to_dataclassimportDataclassFromDict,field_from_dict# Declare dataclass fields with field_from_dict@dataclassclassMyDataclass(DataclassFromDict):my_string:str=field_from_dict()my_int:int=field_from_dict()my_date:datetime=field_from_dict()# Create a dataclass instance using the from_dict constructororigin_dict={"my_string":"Hello","my_int":123,"my_date":"2020-10-11T13:21:23.396748",}dataclass_instance=MyDataclass.from_dict(origin_dict)# Now our dataclass instance has the values from the dictionary>>>dataclass_instance.my_string"Hello">>>dataclass_instance.my_int123>>>dataclass_instance.my_datedatetime.datetime(2020,10,11,13,21,23,396748)Why?You can create a dataclass instance from a dictionary already by unpacking the dictionary values and passing them to the dataclass constructor like this:origin_dict={"my_string":"Hello","my_int":123,"my_date":"2020-10-11T13:21:23.396748",}dataclass_instance=MyDataclass(**origin_dict)However, that method doesn't work when we need to considerType validationType conversion, e.g. an ISO string to adatetimeinstanceDifferences between dictionary keys and dataclass field namesComplex structures with nested dictionaries and listsInstallationdict_to_dataclasscan be installed usingpippipinstalldict-to-dataclassFinding dictionary valuesYou may have noticed that we don't need to specify where to look in the dictionary for field values. That's because by default, the name given to the field in the dataclass is used. It even works if the key in the dictionary is in camelCase:@dataclassclassMyDataclass(DataclassFromDict):my_field:str=field_from_dict()origin_dict={"myField":"field value"}dataclass_instance=MyDataclass.from_dict(origin_dict)>>>dataclass_instance.my_field"field value"It's probably quite common that your dataclass fields have the same names as the dictionary keys they map to but in case they don't, you can pass the dictionary key as the first argument (or thedict_keykeyword argument) tofield_from_dict:@dataclassclassMyDataclass(DataclassFromDict):name_in_dataclass:str=field_from_dict("nameInDictionary")origin_dict={"nameInDictionary":"field value"}dataclass_instance=MyDataclass.from_dict(origin_dict)>>>dataclass_instance.name_in_dataclass"field value"Nested data classesNested dictionaries can be represented by nested dataclasses.@dataclassclassChild(DataclassFromDict):my_field:str=field_from_dict()@dataclassclassParent(DataclassFromDict):child_field:Child=field_from_dict()origin_dict={"child_field":{"my_field":"Hello"}}dataclass_instance=Parent.from_dict(origin_dict)>>>dataclass_instance.child_field.my_field"Hello"ListsList types are handled but the type of the list's items must be specified in the dataclass field type so that we know how to convert them.@dataclassclassMyDataclass(DataclassFromDict):my_list_field:List[str]=field_from_dict()origin_dict={"my_list_field":["First","Second","Third"]}dataclass_instance=MyDataclass.from_dict(origin_dict)>>>dataclass_instance.my_list_field["First","Second","Third"]If we were to use the more generictyping.Listorlistas the field type, an error would be raised when converting the dictionary (there's more info on errors later).@dataclassclassMyDataclass(DataclassFromDict):name_in_dataclass:List=field_from_dict("nameInDictionary")origin_dict={"my_list_field":["First","Second","Third"]}# Here, an UnspecificListFieldError is raiseddataclass_instance=MyDataclass.from_dict(origin_dict)Lists of other dataclasses are also supported.@dataclassclassChild(DataclassFromDict):name:str=field_from_dict()@dataclassclassParent(DataclassFromDict):children:List[Child]=field_from_dict()origin_dict={"children":[{"name":"Jane"},{"name":"Joe"},]}dataclass_instance=Parent.from_dict(origin_dict)>>>dataclass_instance.children[Child(name='Jane'),Child(name='Joe')]Value conversionIf the value found in the dictionary doesn't match the dataclass field type, the dictionary value can be converted.DatetimeDataclass fields of typedatetimeare handled and can be converted fromStrings (handled bydateutil)Python-style timestamps of typefloat, e.g.1602436272.681808Javascript-style timestamps of typeint, e.g.1602436323268EnumDataclass fields with anEnumtype can also be converted by default:classNumber(Enum):ONE=1TWO=2THREE=3@dataclassclassMyDataclass(DataclassFromDict):number:Number=field_from_dict()dataclass_instance=MyDataclass.from_dict({"number":"TWO"})>>>dataclass_instance.number<Number.TWO:2>The value in the dictionary should be the name of the Enum value as a string. If the value is not found, anEnumValueNotFoundErroris raised.Custom convertersIf you need to convert a dictionary value that isn't covered by the defaults, you can pass in a converter function usingfield_from_dict'sconverterparameter:defyes_no_to_bool(yes_no:str)->bool:returnyes_no=="yes"@dataclassclassMyDataclass(DataclassFromDict):is_yes:bool=field_from_dict(converter=yes_no_to_bool)dataclass_instance=MyDataclass.from_dict({"is_yes":"yes"})>>>dataclass_instance.is_yesTrueADictValueConversionErroris raised if the dictionary value cannot be converted.Optional typesIf you expect that the dictionary value for a field might beNone, the dataclass field should be given anOptionaltype.@dataclassclassMyDataclass(DataclassFromDict):my_field:Optional[str]=field_from_dict()dataclass_instance=MyDataclass.from_dict({"myField":None})>>>dataclass_instance.my_fieldNoneIfmy_fieldabove had the typestrinstead, aDictValueNotFoundErrorwould be raised.Missing valuesIf you expect that the field might be missing from the dictionary, you should provide a value to either thedefaultordefault_factoryparameters offield_from_dict. These are passed through to the underlyingdataclasses.fieldcall, which you can read abouthere.If no default value is provided and the key is not found in the dictionary, aDictKeyNotFoundErroris raised.@dataclassclassMyDataclass(DataclassFromDict):my_field:str=field_from_dict(default="default value")my_list_field:str=field_from_dict(default_factory=list)dataclass_instance=MyDataclass.from_dict({})>>>dataclass_instance.my_field"default value">>>dataclass_instance.my_list_field[]Note that if the field exists in the dictionary and has a value ofNone,defaultanddefault_factoryarenotused. You would still need to give the field anOptionaltype.@dataclassclassMyDataclass(DataclassFromDict):my_field:str=field_from_dict(default="default value")# Here, a DictValueNotFoundError is raiseddataclass_instance=MyDataclass.from_dict({"myField":None})Data validationA side effect of converting a dictionary to a dataclass instance is that the data in the dictionary is validated, which can be useful on its own. For example, imagine we're writing a handler for a POST method in a REST API. If we use aDataclassFromDictto describe the request body, we can validate the user's input by attempting to convert it to a dataclass instance.@dataclassclassCreateResourceBody(DataclassFromDict):...fields@app.route("/resource",methods=["POST"])defcreate_resource():body_dict=request.get_json()try:body_dataclass_instance=CreateResourceBody.from_dict(body_dict)exceptDataclassFromDictError:return"Bad request",400# Create the resource with with body_dataclass_instance