I'm wondering how could I transform nested dataclasses to dict except for None
fields. I'm aware of asdict()
method existence, but I'm trying to write method similar to asdict()
that will ignore empty values during dict
creation.
For example:
@dataclass
class Nested:
name: str = None
contacts: list = field(default_factory = list)
@dataclass
class Main:
nested: Nested = field(default_factory = Nested)
surname: str = None
Expected result would be:
example = Main()
example_d = asdict(example)
print(example_d)
{"nested": {"contacts": []}}
What I have currently:
example = Main()
example_d = asdict(example)
print(example_d)
{"nested": {"name": None, "contacts": []}, surname: None}
I know that asdict()
would ignore fields without value and given default as default, but I need = None
during dataclass initialization.
I ended up overriding the asdict()
and _asdict_inner()
methods:
def asdict(obj, *, dict_factory=dict):
if not _is_dataclass_instance(obj):
raise TypeError("asdict() should be called on dataclass instances")
return _asdict_inner(obj, dict_factory)
def _asdict_inner(obj, dict_factory):
if _is_dataclass_instance(obj):
result = []
for f in fields(obj):
value = _asdict_inner(getattr(obj, f.name), dict_factory)
result.append((f.name, value))
return dict_factory(result)
elif isinstance(obj, tuple) and hasattr(obj, '_fields'):
return type(obj)(*[_asdict_inner(v, dict_factory) for v in obj])
elif isinstance(obj, (list, tuple)):
return type(obj)(_asdict_inner(v, dict_factory) for v in obj)
elif isinstance(obj, dict):
return type(obj)((_asdict_inner(k, dict_factory),
_asdict_inner(v, dict_factory))
for k, v in obj.items() if v is not None) # <- Mine change to exclude None values and keys.
else:
return copy.deepcopy(obj)
But it doesn't work as supposed.
Implementation of _is_dataclass_instance
as requested. It's from the dataclass
module.
def _is_dataclass_instance(obj):
"""Returns True if obj is an instance of a dataclass."""
return not isinstance(obj, type) and hasattr(obj, _FIELDS)
You could create a custom dictionary factory that drops None
valued keys and use it with asdict()
.
class CustomDict(dict):
def __init__(self, data):
super().__init__(x for x in data if x[1] is not None)
example = Main()
example_d = asdict(example, dict_factory=CustomDict)
Edit: Based on @user2357112-supports-monica suggestion, here's an alternative that does not use custom dictionaries.
def factory(data):
return dict(x for x in data if x[1] is not None)
example = Main()
example_d = asdict(example, dict_factory=factory)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With