Created
February 19, 2019 15:08
-
-
Save gatopeich/1efd3e1e4269e1e98fae9983bb914f22 to your computer and use it in GitHub Desktop.
Python 3.7 dataclass to/from dict/json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from dataclasses import dataclass, fields as datafields | |
from ujson import dumps, loads | |
# Note: ujson seamlessly serializes dataclasses, unlike stdlib's json | |
@dataclass | |
class Point: | |
x: float | |
y: float | |
# Shallow dataclass can be rebuilt from dict/json: | |
point = Point(1,2) | |
assert point == Point(**loads(dumps(point))) | |
# However, deep dataclass's fields won't be rebuilt from the json dict: | |
@dataclass | |
class Line: | |
a: Point | |
b: Point | |
line = Line(Point(1,2), Point(3,4)) | |
assert line != Line(**loads(dumps(line))) | |
print(line, '\n !=', Line(**loads(dumps(line)))) | |
# Line(a=Point(x=1, y=2), b=Point(x=3, y=4)) | |
# != Line(a={'x': 1, 'y': 2}, b={'x': 3, 'y': 4}) | |
# But we can simply reconstruct recursively: | |
def dataclass_from_dict(klass, dikt): | |
try: | |
fieldtypes = {f.name:f.type for f in datafields(klass)} | |
return klass(**{f:dataclass_from_dict(fieldtypes[f],dikt[f]) for f in dikt}) | |
except: | |
return dikt | |
line_from_dict = dataclass_from_dict(Line,loads(dumps(line))) | |
assert line == line_from_dict |
I think it should be easy to add support for lists
and tuples
:
def dataclass_from_dict(klass, dikt):
try:
fieldtypes = klass.__annotations__ # this can be also simplified I believe
return klass(**{f: dataclass_from_dict(fieldtypes[f], dikt[f]) for f in dikt})
except Exception:
if isinstance(dikt, (tuple, list)):
return [dataclass_from_dict(klass.__args__[0], f) for f in dikt]
return dikt
This assumes that your type in dataclass is defined using typing
module.
Example:
@dataclass
class Lines:
lines: List[Line]
lines = Lines(lines=[
Line(Point(1, 2), Point(3, 4)),
Line(Point(3, 4), Point(5, 6))
])
dataclass_from_dict(Lines, asdict(lines)))
Note: I think using asdict
is fine, haven't noticed any problem so far.
I think your solution is good. I would just change the type of the exception (and by the way correct the d by dikt in the exception).
def dataclass_from_dict(klass, dikt):
try:
fieldtypes = klass.__annotations__
return klass(**{f: dataclass_from_dict(fieldtypes[f], dikt[f]) for f in dikt})
except AttributeError:
if isinstance(dikt, (tuple, list)):
return [dataclass_from_dict(klass.__args__[0], f) for f in dikt]
return dikt
@EnlNovius Thanks, I've missed the d
. Also using AttributeError
makes probably more sense, cannot think of any potential problem.
Using annotations you remove all inherited fields
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
note: will not work for fields with type of list