Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dict from nested dataclasses

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)
like image 272
slqq Avatar asked Dec 25 '19 21:12

slqq


1 Answers

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)
like image 182
Venkatesh-Prasad Ranganath Avatar answered Sep 18 '22 13:09

Venkatesh-Prasad Ranganath