Skip to content

Instantly share code, notes, and snippets.

@jhidding
Last active February 14, 2022 09:54
Show Gist options
  • Save jhidding/ccece4b6184d5827c4a9a72ba399eed9 to your computer and use it in GitHub Desktop.
Save jhidding/ccece4b6184d5827c4a9a72ba399eed9 to your computer and use it in GitHub Desktop.
Convert a nested structure of dict and list to dataclasses
import typing
from dataclasses import is_dataclass
def isgeneric(annot):
return hasattr(annot, "__origin__") \
and hasattr(annot, "__args__")
def construct(annot, json):
"""Construct an object from a given type from a JSON stream.
The `annot` type should be one of: str, int, list[T], Optional[T],
or a dataclass, and the JSON data should match exactly the given
definitions in the dataclass hierarchy.
"""
if annot is str:
assert isinstance(json, str)
return json
if annot is int:
assert isinstance(json, int)
return json
if isgeneric(annot) and typing.get_origin(annot) is list:
assert isinstance(json, list)
return [construct(typing.get_args(annot)[0], item) for item in json]
if isgeneric(annot) and typing.get_origin(annot) is Union \
and typing.get_args(annot)[1] is types.NoneType:
if json is None:
return None
else:
return construct(typing.get_args(annot)[0], json)
if is_dataclass(annot):
assert isinstance(json, dict)
arg_annot = typing.get_type_hints(annot)
assert all(k in json for k in arg_annot)
args = { k: construct(v, json[k])
for k, v in arg_annot.items() }
return annot(**args)
@v1kko
Copy link

v1kko commented Feb 14, 2022

construct(list(list()), "[['a'],['b']]")

Something like this?

@v1kko
Copy link

v1kko commented Feb 14, 2022

no I misunderstood, the json is already classes

construct(list(list()), [['a']['b']])

would work

@jhidding
Copy link
Author

jhidding commented Feb 14, 2022

Actually:

>>> construct(list[list[str]], [['a'],['b']])
[['a'],['b']]

which is a bit silly, but at least you have validated the schema. A better example:

@dataclass
class Pt:
    a: int
    b: int

construct(list[Pt], [{'a': 1, 'b': 2}, {'a': 3, 'b': 4}])
#> [Pt(1, 2), Pt(3, 4)]

The proper way to go is to use the dacite libary, which does pretty much the same (https://github.com/konradhalas/dacite)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment